This commit is contained in:
Flatlogic Bot 2025-12-03 22:04:46 +00:00
parent f745cb0f27
commit 20dd5c8f61
42 changed files with 2923 additions and 885 deletions

61
about.php Normal file
View File

@ -0,0 +1,61 @@
<?php
$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>
<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>
</div>
</div>
</div>
<!-- Team Section or Values -->
<section class="py-5 mt-4">
<div class="row text-center g-4">
<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>
<h4 class="fw-bold">تعهد به کیفیت</h4>
<p class="text-muted">استفاده از بهترین مواد اولیه و کنترل کیفی دقیق در تمام مراحل تولید.</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>
<h4 class="fw-bold">هنر دست</h4>
<p class="text-muted">تمام محصولات ما با عشق و دقت توسط هنرمندان ماهر ساخته می‌شوند.</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>
<h4 class="fw-bold">طراحی ماندگار</h4>
<p class="text-muted">خلق آثاری مدرن و در عین حال کلاسیک که هیچ‌گاه از مد نمی‌افتند.</p>
</div>
</div>
</div>
</section>
</main>
<?php require_once 'includes/footer.php'; ?>

View File

@ -2,80 +2,78 @@
session_start();
require_once __DIR__ . '/auth_check.php';
// New header
require_once __DIR__ . '/header.php';
$flash_message = $_SESSION['flash_message'] ?? null;
if ($flash_message) {
unset($_SESSION['flash_message']);
}
?>
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>افزودن محصول جدید</title>
<meta name="robots" content="noindex, nofollow">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../assets/css/custom.css?v=<?php echo time(); ?>">
<!-- SweetAlert2 -->
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
</head>
<body class="bg-dark text-white">
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="font-lalezar">افزودن محصول جدید</h1>
<a href="index.php" class="btn btn-outline-light">بازگشت</a>
</div>
<div class="card bg-dark-2">
<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 bg-dark text-white" id="name" name="name" required>
</div>
<div class="mb-3">
<label for="description" class="form-label">توضیحات</label>
<textarea class="form-control bg-dark text-white" id="description" name="description" rows="3" required></textarea>
</div>
<div class="mb-3">
<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>
<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 bg-dark text-white" id="price" name="price" required>
<input type="number" class="form-control" id="price" name="price" required>
</div>
<div class="mb-3">
<div class="col-md-6 mb-3">
<label for="image" class="form-label">تصویر محصول</label>
<input type="file" class="form-control bg-dark text-white" id="image" name="image" accept="image/*" required>
<input type="file" class="form-control" id="image" name="image" accept="image/*">
</div>
<div class="mb-3">
<label for="colors" class="form-label">کدهای رنگ (اختیاری)</label>
<input type="text" class="form-control bg-dark text-white" id="colors" name="colors" placeholder="مثال: #8B4513, #2C2C2C">
<div class="form-text">کدهای رنگ هگزادسیمال را با کاما جدا کنید.</div>
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="is_featured" name="is_featured" value="1">
<label class="form-check-label" for="is_featured">محصول ویژه</label>
</div>
<button type="submit" class="btn btn-primary w-100">افزودن محصول</button>
</form>
</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>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- Page-specific scripts -->
<script>
document.addEventListener('DOMContentLoaded', function () {
<?php if ($flash_message): ?>
Swal.fire({
title: '<?php echo $flash_message["type"] === "success" ? "عالی" : "خطا"; ?>',
html: '<?php echo addslashes($flash_message["message"]); ?>', // Use html to render <br> tags
html: '<?php echo addslashes($flash_message["message"]); ?>',
icon: '<?php echo $flash_message["type"]; ?>',
confirmButtonText: 'باشه'
confirmButtonText: 'باشه',
background: 'var(--admin-surface)',
color: 'var(--admin-text-primary)'
});
<?php endif; ?>
});
</script>
</body>
</html>
<?php
// New footer
require_once __DIR__ . '/footer.php';
?>

View File

@ -0,0 +1,259 @@
/* =================================================================
ADMIN PANEL MODERN STYLES
================================================================= */
: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;
}
/* --- General Body & Typography --- */
body.admin-page {
background-color: var(--admin-bg);
color: var(--admin-text-primary);
font-family: var(--admin-font);
padding-right: 0; /* Reset previous style */
}
.admin-main-content {
padding: 2rem;
margin-right: 280px; /* Space for the new sidebar */
transition: margin-right 0.3s ease;
}
h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
color: var(--admin-text-primary);
font-weight: 700;
}
a {
color: var(--admin-accent);
}
a:hover {
color: var(--admin-accent-hover);
}
/* --- 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 {
flex-grow: 1;
padding-top: 1rem;
}
.admin-sidebar .nav-link {
color: var(--admin-text-secondary);
display: flex;
align-items: center;
font-size: 1rem;
font-weight: 500;
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);
font-weight: 600;
}
.admin-sidebar .nav-link .bi {
font-size: 1.2rem;
margin-left: 0.75rem; /* For RTL, it should be margin-left */
}
.admin-sidebar-footer {
padding: 1rem 1.5rem;
border-top: 1px solid var(--admin-border);
}
/* --- Dashboard Stat Cards --- */
.stat-card {
background-color: var(--admin-surface);
border: 1px solid var(--admin-border);
border-radius: 0.75rem;
padding: 1.5rem;
display: flex;
align-items: center;
transition: all 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);
}
.stat-card .icon-container {
font-size: 2rem;
color: var(--admin-accent);
background-color: #363e4d;
width: 60px;
height: 60px;
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;
}
.stat-card .stat-info p {
margin: 0;
color: var(--admin-text-secondary);
}
/* --- Dashboard Tables & Badges --- */
.card-table .card-header {
background-color: transparent;
border-bottom: 1px solid var(--admin-border);
padding: 1rem 1.5rem;
font-weight: 600;
}
.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 */
/* --- Modal Styling --- */
.modal-content {
background-color: var(--admin-surface);
color: var(--admin-text-primary);
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%);
}
@media (max-width: 992px) {
.admin-main-content {
margin-right: 0;
}
.admin-sidebar {
transform: translateX(280px); /* For RTL */
transition: transform 0.3s ease;
}
.admin-sidebar.is-open {
transform: translateX(0);
}
/* 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;
}
}

View File

@ -1,5 +1,7 @@
<?php
session_start();
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Check if the user is logged in. If not, redirect to the login page.
if (!isset($_SESSION['is_admin']) || $_SESSION['is_admin'] !== true) {

View File

@ -3,15 +3,12 @@ session_start();
require_once __DIR__ . '/auth_check.php';
require_once __DIR__ . '/../db/config.php';
$flash_message = $_SESSION['flash_message'] ?? null;
if ($flash_message) {
unset($_SESSION['flash_message']);
}
// Sanitize and validate product ID
$product_id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
$product_id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
if ($product_id <= 0) {
header('Location: index.php');
if (!$product_id) {
$_SESSION['flash_message'] = ['type' => 'danger', 'message' => 'شناسه محصول نامعتبر است.'];
header('Location: products.php');
exit;
}
@ -22,88 +19,114 @@ try {
$product = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$product) {
header('Location: index.php');
$_SESSION['flash_message'] = ['type' => 'danger', 'message' => 'محصول مورد نظر یافت نشد.'];
header('Location: products.php');
exit;
}
} catch (PDOException $e) {
die("Error fetching product: " . $e->getMessage());
error_log("Database Error: " . $e->getMessage());
$_SESSION['flash_message'] = ['type' => 'danger', 'message' => 'خطا در اتصال به پایگاه داده. لطفاً بعداً تلاش کنید.'];
header('Location: products.php');
exit;
}
$page_title = "ویرایش محصول: " . htmlspecialchars($product['name']);
require_once 'header.php';
?>
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ویرایش محصول: <?php echo htmlspecialchars($product['name']); ?></title>
<meta name="robots" content="noindex, nofollow">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../assets/css/custom.css?v=<?php echo time(); ?>">
<!-- SweetAlert2 -->
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
</head>
<body class="bg-dark text-white">
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-8">
<h1 class="font-lalezar mb-4">ویرایش محصول</h1>
<div class="card bg-dark-2">
<div class="card-body p-4">
<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="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>
</div>
<div class="mb-3">
<label for="name" class="form-label">نام محصول</label>
<input type="text" class="form-control bg-dark text-white" id="name" name="name" value="<?php echo htmlspecialchars($product['name']); ?>" required>
<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']); ?>">
<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="mb-3">
<label for="description" class="form-label">توضیحات</label>
<textarea class="form-control bg-dark text-white" id="description" name="description" rows="3" required><?php echo htmlspecialchars($product['description']); ?></textarea>
<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 class="mb-3">
<label for="price" class="form-label">قیمت (به تومان)</label>
<input type="number" class="form-control bg-dark text-white" id="price" name="price" min="0" value="<?php echo htmlspecialchars($product['price']); ?>" required>
</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>
<!-- 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="mb-3">
<label for="colors" class="form-label">رنگ‌ها</label>
<input type="text" class="form-control bg-dark text-white" id="colors" name="colors" value="<?php echo htmlspecialchars($product['colors'] ?? ''); ?>">
<div class="form-text">رنگ‌های موجود را با کاما از هم جدا کنید (مثال: #FFFFFF, #000000).</div>
</div>
<div class="mb-3">
<label for="image" class="form-label">تصویر محصول</label>
<input type="file" class="form-control bg-dark text-white" id="image" name="image" accept="image/*">
<div class="form-text mt-2">تصویر فعلی:</div>
<img src="../<?php echo htmlspecialchars($product['image_url']); ?>" alt="Current Image" class="img-thumbnail mt-2" width="100">
</div>
<div class="mb-3 form-check">
<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="d-flex justify-content-end gap-2 mt-4">
<a href="index.php" class="btn btn-secondary">انصراف</a>
<button type="submit" class="btn btn-primary">به‌روزرسانی محصول</button>
</div>
</form>
</div>
</div>
</div>
</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>
</form>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<?php
require_once 'footer.php';
?>
<script>
document.addEventListener('DOMContentLoaded', function () {
<?php if ($flash_message): ?>
Swal.fire({
title: '<?php echo $flash_message["type"] === "success" ? "عالی" : "خطا"; ?>',
html: '<?php echo addslashes($flash_message["message"]); ?>', // Use html to render <br> tags
icon: '<?php echo $flash_message["type"]; ?>',
confirmButtonText: 'باشه'
});
<?php endif; ?>
});
// 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]);
}
}
</script>
</body>
</html>

7
admin/footer.php Normal file
View File

@ -0,0 +1,7 @@
</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>
</body>
</html>

View File

@ -1,10 +1,9 @@
<?php
session_start();
require_once __DIR__ . '/../db/config.php';
require_once __DIR__ . '/auth_check.php';
$action = $_REQUEST['action'] ?? '';
$pdo = db();
// Default redirect location
@ -19,7 +18,7 @@ switch ($action) {
$price = filter_var($_POST['price'], FILTER_VALIDATE_FLOAT);
$colors = trim($_POST['colors'] ?? '');
$is_featured = isset($_POST['is_featured']) ? 1 : 0;
$errors = [];
// Validation
@ -30,18 +29,18 @@ switch ($action) {
$image_path = '';
if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) {
$upload_dir = __DIR__ . '/../assets/images/products/';
if (!is_dir($upload_dir)) {
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.";
$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 {
@ -49,121 +48,122 @@ switch ($action) {
}
}
} 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 error: Missing a temporary folder for uploads.",
UPLOAD_ERR_CANT_WRITE => "Server error: Failed to write the uploaded file to disk.",
UPLOAD_ERR_EXTENSION => "A PHP extension prevented the file upload.",
$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.",
];
$error_message = $upload_errors[$file_error] ?? "An unknown upload error occurred (Code: {$file_error}).";
// Only trigger error if the action is 'add', where image is mandatory
if ($action === 'add') {
$errors[] = "Image Upload Failed: " . $error_message;
if ($file_error !== UPLOAD_ERR_NO_FILE) {
$errors[] = $upload_errors[$file_error] ?? "An unknown error occurred during file upload.";
}
}
if (empty($errors)) {
try {
$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 = 'index.php';
} catch (PDOException $e) {
$_SESSION['flash_message'] = ['type' => 'error', 'message' => 'خطا در افزودن محصول: ' . $e->getMessage()];
}
$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 {
$error_message = 'لطفاً تمام خطاها را برطرف کنید:<br><br>' . implode('<br>', $errors);
$_SESSION['flash_message'] = ['type' => 'error', 'message' => $error_message];
$_SESSION['flash_message'] = ['type' => 'error', 'message' => implode("<br>", $errors)];
}
}
break;
case 'edit':
$id = $_POST['id'] ?? $_GET['id'] ?? null;
$redirect_to = 'edit_product.php?id=' . $id;
$redirect_to = 'products.php'; // Default redirect on success or if ID is missing
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$id = filter_var($id, FILTER_VALIDATE_INT);
$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[] = "شناسه محصول نامعتبر است.";
}
// Other validations...
$image_path = $_POST['current_image'] ?? '';
$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/';
$filename = uniqid('product_', true) . '_' . basename($_FILES['image']['name']);
$target_file = $upload_dir . $filename;
if (move_uploaded_file($_FILES['image']['tmp_name'], $target_file)) {
if (!empty($image_path) && file_exists(__DIR__ . '/../' . $image_path)) {
unlink(__DIR__ . '/../' . $image_path);
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.";
}
$image_path = 'assets/images/products/' . $filename;
} else {
$errors[] = "خطا در آپلود تصویر جدید.";
$errors[] = "Image directory is not writable.";
}
}
if (empty($errors)) {
try {
$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 = 'index.php';
} catch (PDOException $e) {
$_SESSION['flash_message'] = ['type' => 'error', 'message' => 'خطا در ویرایش محصول: ' . $e->getMessage()];
}
$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 {
$error_message = 'فرم دارای خطا است:<br><br>' . implode('<br>', $errors);
$_SESSION['flash_message'] = ['type' => 'error', 'message' => $error_message];
$_SESSION['flash_message'] = ['type' => 'error', 'message' => implode("<br>", $errors)];
}
}
break;
case 'delete':
$id = filter_var($_GET['id'], FILTER_VALIDATE_INT);
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
if ($id) {
try {
// First, get the image path to delete the file
$stmt = $pdo->prepare("SELECT image_url FROM products WHERE id = ?");
$stmt->execute([$id]);
$image_to_delete = $stmt->fetchColumn();
// 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);
// Delete the record
$sql = "DELETE FROM products WHERE id = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$id]);
// If record deleted, delete the file
if ($stmt->rowCount() > 0 && $image_to_delete && file_exists(__DIR__ . '/../' . $image_to_delete)) {
unlink(__DIR__ . '/../' . $image_to_delete);
if ($product && !empty($product['image_url'])) {
$image_file = __DIR__ . '/../' . $product['image_url'];
if (file_exists($image_file)) {
// unlink($image_file);
}
$_SESSION['flash_message'] = ['type' => 'success', 'message' => 'محصول با موفقیت حذف شد.'];
} catch (PDOException $e) {
$_SESSION['flash_message'] = ['type' => 'error', 'message' => 'خطا در حذف محصول: ' . $e->getMessage()];
}
// 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' => 'شناسه محصول نامعتبر است.'];
$_SESSION['flash_message'] = ['type' => 'error', 'message' => 'شناسه محصول برای حذف نامعتبر است.'];
}
$redirect_to = 'index.php';
$redirect_to = 'products.php';
break;
default:
$_SESSION['flash_message'] = ['type' => 'error', 'message' => 'عملیات نامعتبر است.'];
break;
}
// Redirect back after the action
header('Location: ' . $redirect_to);
header("Location: " . $redirect_to);
exit;

27
admin/header.php Normal file
View File

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<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">
<link rel="stylesheet" href="assets/css/admin_style.css?v=<?php echo time(); ?>">
<!-- SweetAlert2 -->
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
</head>
<body class="admin-page">
<?php require_once 'nav.php'; ?>
<main class="admin-main-content">
<div class="container-fluid">

View File

@ -3,12 +3,24 @@ 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;
$total_products = 0;
$total_orders = 0;
$recent_orders = [];
try {
$pdo = db();
$stmt = $pdo->query("SELECT id, name, price FROM products ORDER BY created_at DESC");
$products = $stmt->fetchAll(PDO::FETCH_ASSOC);
$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);
} catch (PDOException $e) {
die("Error fetching products: " . $e->getMessage());
$dashboard_error = "<strong>خطا در بارگذاری اطلاعات داشبورد:</strong> " . $e->getMessage();
$dashboard_error .= "<br><br>این خطا معمولاً به دلیل قدیمی بودن ساختار دیتابیس رخ می‌دهد. لطفاً برای به‌روزرسانی به <a href='../migrate.php' class='alert-link'>صفحه مایگریشن</a> بروید.";
}
$flash_message = $_SESSION['flash_message'] ?? null;
@ -16,102 +28,115 @@ if ($flash_message) {
unset($_SESSION['flash_message']);
}
// Function to map status to a badge class
function get_status_badge($status) {
switch (strtolower($status)) {
case 'processing':
return 'bg-processing';
case 'shipped':
return 'bg-shipped';
case 'cancelled':
return 'bg-cancelled';
default:
return 'bg-pending';
}
}
?>
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>پنل مدیریت - محصولات</title>
<meta name="robots" content="noindex, nofollow">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../assets/css/custom.css?v=<?php echo time(); ?>">
<!-- SweetAlert2 -->
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
</head>
<body class="bg-dark text-white">
<div class="container mt-5">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="font-lalezar">مدیریت محصولات</h1>
<div class="d-flex gap-2">
<a href="add_product.php" class="btn btn-success">+ افزودن محصول جدید</a>
<a href="logout.php" class="btn btn-outline-danger">خروج</a>
<h1 class="h2 mb-4">داشبورد</h1>
<?php if ($dashboard_error): ?>
<div class="alert alert-danger"><?php echo $dashboard_error; ?></div>
<?php else: ?>
<!-- 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>
<div class="table-responsive">
<table class="table table-dark table-striped table-hover">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">نام محصول</th>
<th scope="col">قیمت</th>
<th scope="col">عملیات</th>
</tr>
</thead>
<tbody>
<?php if (empty($products)): ?>
<tr>
<td colspan="4" class="text-center">هیچ محصولی یافت نشد.</td>
</tr>
<?php else: ?>
<?php foreach ($products as $product): ?>
<!-- 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 scope="row"><?php echo htmlspecialchars($product['id']); ?></th>
<td><?php echo htmlspecialchars($product['name']); ?></td>
<td><?php echo number_format($product['price']); ?> تومان</td>
<td>
<a href="edit_product.php?id=<?php echo $product['id']; ?>" class="btn btn-sm btn-primary">ویرایش</a>
<a href="handler.php?action=delete&id=<?php echo $product['id']; ?>" class="btn btn-sm btn-danger delete-btn">حذف</a>
</td>
<th>شماره سفارش</th>
<th>نام مشتری</th>
<th>مبلغ کل</th>
<th>وضعیت</th>
<th>تاریخ</th>
<th class="text-end"></th>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</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>
<div class="mt-4">
<a href="../index.php" class="btn btn-outline-light">بازگشت به سایت</a>
</div>
</div>
<?php endif; ?>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- Page-specific scripts -->
<script>
document.addEventListener('DOMContentLoaded', function () {
// Flash message handling
// Flash message handling (if any)
<?php if ($flash_message): ?>
Swal.fire({
title: '<?php echo $flash_message["type"] === "success" ? "عالی" : "خطا"; ?>',
title: '<?php echo $flash_message["type"] === "success" ? "موفق" : "خطا"; ?>',
html: '<?php echo addslashes($flash_message["message"]); ?>',
icon: '<?php echo $flash_message["type"]; ?>',
confirmButtonText: 'باشه'
confirmButtonText: 'باشه',
background: 'var(--admin-surface)',
color: 'var(--admin-text-primary)'
});
<?php endif; ?>
// 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: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'بله، حذف کن!',
cancelButtonText: 'انصراف'
}).then((result) => {
if (result.isConfirmed) {
window.location.href = href;
}
});
});
});
});
</script>
</body>
</html>
<?php
// New footer
require_once __DIR__ . '/footer.php';
?>

View File

@ -32,6 +32,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
<meta name="robots" content="noindex, nofollow">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../assets/css/custom.css?v=<?php echo time(); ?>">
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
</head>
<body class="bg-dark text-white">
@ -43,7 +44,21 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
<h1 class="font-lalezar text-center mb-4">ورود به پنل</h1>
<p class="text-center text-muted mb-4">رمز عبور: admin123</p>
<?php if ($error): ?>
<div class="alert alert-danger"><?php echo $error; ?></div>
<script>
document.addEventListener('DOMContentLoaded', function() {
Swal.fire({
toast: true,
position: 'top-start',
icon: 'error',
title: '<?php echo $error; ?>',
showConfirmButton: false,
timer: 3000,
timerProgressBar: true,
background: '#2C2C2C',
color: '#D5D5D5'
});
});
</script>
<?php endif; ?>
<form method="POST">
<div class="mb-3">

42
admin/nav.php Normal file
View File

@ -0,0 +1,42 @@
<?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>
</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>
</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>
<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>
<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>
</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>

131
admin/orders.php Normal file
View File

@ -0,0 +1,131 @@
<?php
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");
$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) {
switch (strtolower($status)) {
case 'processing':
return 'bg-processing';
case 'shipped':
return 'bg-shipped';
case 'cancelled':
return 'bg-cancelled';
default:
return 'bg-pending';
}
}
?>
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h2">مدیریت سفارشات</h1>
</div>
<?php if (isset($error_message)): ?>
<div class="alert alert-danger"><?php echo $error_message; ?></div>
<?php endif; ?>
<div class="card shadow-sm card-table" style="background-color: var(--admin-surface); border-color: var(--admin-border);">
<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)): ?>
<tr>
<td colspan="6" class="text-center py-4">هیچ سفارشی یافت نشد.</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>
</div>
</div>
<?php
// New footer
require_once __DIR__ . '/footer.php';
?>

119
admin/products.php Normal file
View File

@ -0,0 +1,119 @@
<?php
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 {
$pdo = db();
$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());
}
$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>
<!-- Products Table -->
<div class="card shadow-sm" style="background-color: var(--admin-surface); border-color: var(--admin-border);">
<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)): ?>
<tr>
<td colspan="4" class="text-center py-4">هیچ محصولی یافت نشد.</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>
</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; ?>
// 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;
}
});
});
});
});
</script>
<?php
// New footer - includes closing tags
require_once __DIR__ . '/footer.php';
?>

1
admin/test.php Normal file
View File

@ -0,0 +1 @@
<?php phpinfo(); ?>

248
assets/css/auth_style.css Normal file
View File

@ -0,0 +1,248 @@
/*
Auth Pages - Dark Theme Overhaul
Using global variables from custom.css
*/
html, body {
height: 100%;
margin: 0;
font-family: var(--body-font);
/* The background is set by the body rule in custom.css */
}
.auth-wrapper {
display: flex;
min-height: 100vh;
width: 100%;
background-color: var(--background-color);
}
.auth-bg {
flex: 1;
background: url('https://images.pexels.com/photos/193902/pexels-photo-193902.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2') no-repeat center center;
background-size: cover;
position: relative;
display: none; /* Hide on small screens */
}
@media (min-width: 992px) {
.auth-bg {
display: flex;
align-items: center;
justify-content: center;
}
}
.auth-bg::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 1;
}
.auth-bg-content {
position: relative;
z-index: 2;
text-align: center;
padding: 40px;
max-width: 450px;
color: var(--white-color);
}
.auth-bg-content h1 {
font-size: 2.5rem;
font-weight: 700;
margin-bottom: 1rem;
text-shadow: 1px 1px 8px rgba(0,0,0,0.7);
}
.auth-bg-content p {
font-size: 1.1rem;
line-height: 1.6;
}
.auth-form-wrapper {
flex-grow: 1; /* Take up all space on mobile */
display: flex;
align-items: center;
justify-content: center;
padding: 30px;
}
@media (min-width: 992px) {
.auth-form-wrapper {
flex: 1;
flex-grow: 0;
}
}
.auth-form-container {
max-width: 420px;
width: 100%;
}
.auth-form-container .form-header h2 {
font-size: 1.8rem;
font-weight: 700;
color: var(--heading-color);
margin-bottom: 0.5rem;
}
.auth-form-container .form-header p {
color: var(--text-color);
margin-bottom: 2rem;
opacity: 0.8;
}
.form-group {
position: relative;
margin-bottom: 1.5rem;
}
.auth-form-wrapper .form-control {
height: 52px;
background-color: var(--surface-color);
border: 1px solid var(--border-color);
color: var(--text-color);
border-radius: 8px;
padding: 0 20px;
font-size: 0.95rem;
transition: all 0.3s ease;
}
.auth-form-wrapper .form-control:focus {
border-color: var(--primary-color);
background-color: var(--surface-color);
box-shadow: 0 0 0 0.25rem rgba(192, 160, 128, 0.2);
}
.auth-form-wrapper .btn-primary {
background-color: var(--primary-color);
border-color: var(--primary-color);
color: var(--heading-color);
height: 50px;
font-size: 1rem;
font-weight: 600;
border-radius: 8px;
transition: all 0.3s ease;
}
.auth-form-wrapper .btn-primary:hover {
background-color: var(--secondary-color);
border-color: var(--secondary-color);
}
.auth-footer {
text-align: center;
margin-top: 1.5rem;
font-size: 0.9rem;
color: var(--text-color);
}
.auth-footer a {
color: var(--primary-color);
font-weight: 600;
}
.auth-footer a:hover {
text-decoration: underline;
}
.alert {
border-radius: 8px;
background-color: var(--surface-color);
color: var(--text-color);
border: 1px solid var(--border-color);
}
.alert-danger {
background-color: rgba(220, 53, 69, 0.15);
border-color: rgba(220, 53, 69, 0.5);
color: #f8d7da;
}
.separator {
display: flex;
align-items: center;
text-align: center;
color: var(--text-color);
opacity: 0.6;
}
.separator::before, .separator::after {
content: '';
flex: 1;
border-bottom: 1px solid var(--border-color);
}
.separator:not(:empty)::before {
margin-right: .5em;
}
.separator:not(:empty)::after {
margin-left: .5em;
}
.btn-google {
background-color: var(--surface-color);
border: 1px solid var(--border-color);
color: var(--text-color);
transition: all 0.3s ease;
}
.btn-google:hover {
background-color: var(--background-color);
border-color: var(--text-color);
}
.btn-google:disabled {
opacity: 0.5;
cursor: not-allowed;
}

View File

@ -1,38 +1,22 @@
/*
:root variables for the default Light Theme
Default Dark Theme Variables.
Light theme has been removed.
*/
:root {
--primary-color: #8B4513; /* SaddleBrown */
--secondary-color: #D2B48C; /* Tan */
--accent-color: #C0A080;
--background-color: #FDFBF7;
--surface-color: #FFFFFF; /* For cards, headers, etc. */
--text-color: #4A4A4A;
--heading-color: #2F2F2F;
--border-color: #EAEAEA;
--footer-bg: #2C2C2C;
--white-color: #FFFFFF;
--font-family-sans-serif: 'Montserrat', sans-serif;
--body-font: 'Montserrat', sans-serif;
}
/*
Variables for the Dark Theme
We will apply these by adding a class="dark" to the <html> tag
*/
html.dark {
--primary-color: #C0A080; /* Lighter tan for accents in dark mode */
--secondary-color: #8B4513; /* Darker brown */
--accent-color: #D2B48C;
--background-color: #1A1A1A; /* Very dark grey, almost black */
--surface-color: #2C2C2C; /* Dark grey for cards and surfaces */
--text-color: #D5D5D5; /* Light grey for body text */
--text-color: #EAEAEA; /* Brighter grey for better readability */
--heading-color: #FFFFFF; /* White for headings */
--border-color: #444444; /* Grey for borders */
--footer-bg: #111111; /* Even darker for footer */
}
--white-color: #FFFFFF;
--font-family-sans-serif: 'Vazirmatn', sans-serif;
--body-font: 'Vazirmatn', sans-serif;
}
body {
font-family: var(--body-font);
@ -64,16 +48,12 @@ a:hover {
.btn-primary {
background-color: var(--primary-color);
border-color: var(--primary-color);
color: var(--background-color); /* To have contrast in both themes */
color: var(--heading-color);
padding: 12px 30px;
font-weight: 600;
border-radius: 50px;
transition: all 0.3s ease-in-out;
}
html.dark .btn-primary {
color: var(--heading-color);
}
.btn-primary:hover, .btn-primary:focus {
background-color: var(--secondary-color);
@ -91,13 +71,13 @@ html.dark .btn-primary {
}
.btn-outline-primary:hover {
background-color: var(--primary-color);
color: var(--white-color);
color: var(--heading-color);
}
/* --- Header --- */
.site-header {
background-color: var(--surface-color);
box-shadow: 0 2px 15px rgba(0,0,0,0.05);
box-shadow: 0 2px 15px rgba(0,0,0,0.2);
border-bottom: 1px solid var(--border-color);
transition: background-color 0.3s ease;
position: sticky;
@ -105,10 +85,6 @@ html.dark .btn-primary {
z-index: 1020;
}
html.dark .site-header {
box-shadow: 0 2px 15px rgba(0,0,0,0.2);
}
.site-header .nav-link,
.site-header a {
color: var(--text-color) !important;
@ -125,14 +101,9 @@ html.dark .site-header {
.site-header .badge {
background-color: var(--primary-color) !important;
color: var(--background-color) !important;
}
html.dark .site-header .badge {
color: var(--heading-color) !important;
}
/* --- Footer --- */
.site-footer {
background-color: var(--footer-bg);
@ -158,23 +129,14 @@ html.dark .site-header .badge {
border-radius: 15px;
overflow: hidden;
transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
box-shadow: 0 5px 20px rgba(0,0,0,0.04);
}
html.dark .product-card {
box-shadow: 0 5px 20px rgba(0,0,0,0.15);
}
.product-card:hover {
transform: translateY(-8px);
box-shadow: 0 15px 30px rgba(0,0,0,0.08);
}
html.dark .product-card:hover {
box-shadow: 0 15px 30px rgba(0,0,0,0.25);
}
.product-card .product-image img {
transition: transform 0.5s ease;
}
@ -217,13 +179,7 @@ html.dark .product-card:hover {
box-shadow: 0 0 0 3px var(--surface-color), 0 0 0 5px var(--primary-color);
}
/* Handle specific colors that need border in light mode */
.color-swatches [style*="#FFFFFF"] + label,
.color-swatches [style*="#ffffff"] + label {
border-color: #dedede;
}
html.dark .color-swatches [style*="#000000"] + label {
.color-swatches [style*="#000000"] + label {
border-color: #555;
}
@ -264,7 +220,6 @@ html.dark .color-swatches [style*="#000000"] + label {
/* --- Responsive Design --- */
@media (max-width: 768px) {
/* About Us Mobile */
#about-us .display-5 {
font-size: 2rem;
@ -291,26 +246,31 @@ html.dark .color-swatches [style*="#000000"] + label {
.site-footer .col-lg-2, .site-footer .col-lg-3, .site-footer .col-lg-4 {
margin-bottom: 30px;
}
/* Center flex items in the footer on mobile */
.site-footer .d-flex {
justify-content: center;
}
}
/* --- Header Icon Fixes for Dark Mode --- */
/* Ensure theme toggle and other header buttons are visible in dark mode */
html.dark .site-header .btn {
color: var(--text-color); /* Use the light text color from dark theme variables */
.site-header .btn {
color: var(--text-color);
}
html.dark .site-header .btn:hover {
color: var(--primary-color); /* Use a hover color */
.site-header .btn:hover {
color: var(--primary-color);
}
/* Fix for bootstrap's default hamburger icon in dark mode */
html.dark .navbar-toggler-icon {
.navbar-toggler-icon {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
}
/* Fix for the border color of the toggler button itself */
html.dark .navbar-toggler {
.navbar-toggler {
border-color: rgba(255, 255, 255, 0.15);
}
@ -322,8 +282,8 @@ html.dark .navbar-toggler {
/* --- Custom Gold Button --- */
.btn-outline-gold {
border-color: #D4AF37; /* Gold */
color: #D4AF37;
border-color: var(--accent-color);
color: var(--accent-color);
padding: 12px 30px;
font-weight: 600;
border-radius: 50px;
@ -331,20 +291,11 @@ html.dark .navbar-toggler {
}
.btn-outline-gold:hover {
background-color: #D4AF37;
color: var(--white-color);
}
html.dark .btn-outline-gold {
border-color: var(--accent-color);
color: var(--accent-color);
}
html.dark .btn-outline-gold:hover {
background-color: var(--accent-color);
color: var(--heading-color);
}
/* --- Hero Section Text --- */
.hero-section {
position: relative;
@ -368,10 +319,514 @@ html.dark .btn-outline-gold:hover {
text-shadow: 0 2px 8px rgba(0,0,0,0.5);
}
/* --- About Us Section Text Color Fix for Dark Mode --- */
html.dark #about-us h2,
html.dark #about-us p {
color: var(--heading-color) !important; /* Use a light, readable color */
/* --- About Us Section Text Color Fix --- */
#about-us p {
color: var(--text-color) !important;
}
/* --- Footer Social Icons --- */
.social-icon {
font-size: 1.5rem; /* Larger icons */
transition: all 0.3s ease;
display: inline-block; /* Needed for transform */
color: #AFAFAF !important; /* Default icon color from footer */
}
.social-icon:hover {
transform: translateY(-3px) scale(1.1); /* Lift and grow effect */
}
/* Brand Colors on Hover */
.social-icon.bi-instagram:hover { color: #E4405F !important; }
.social-icon.bi-telegram:hover { color: #26A5E4 !important; }
.social-icon.bi-whatsapp:hover { color: #25D366 !important; }
/* --- Admin Panel Sidebar --- */
body.admin-page {
padding-right: 260px; /* Make space for the sidebar */
transition: padding-right 0.3s ease;
}
.admin-sidebar {
position: fixed;
top: 0;
right: 0;
height: 100vh;
width: 260px;
z-index: 1030;
display: flex;
flex-direction: column;
transition: right 0.3s ease;
}
.admin-sidebar h3 {
font-family: 'Lalezar', cursive;
font-size: 1.5rem;
}
.admin-sidebar .nav-link {
color: var(--text-color);
font-size: 1.05rem;
padding: 15px 25px;
border-right: 4px solid transparent;
transition: all 0.3s ease;
}
.admin-sidebar .nav-link:hover {
background-color: rgba(255, 255, 255, 0.05);
border-right-color: var(--accent-color);
color: var(--white-color);
}
.admin-sidebar .nav-link.active {
background-color: var(--primary-color);
color: var(--heading-color);
border-right-color: var(--accent-color);
font-weight: 700;
}
.admin-sidebar .sidebar-footer {
margin-top: auto;
padding: 25px;
border-top: 1px solid var(--border-color);
}
.admin-main-content {
padding: 2rem;
}
/* Responsive Admin Layout */
@media (max-width: 992px) {
body.admin-page {
padding-right: 0;
}
.admin-sidebar {
right: -260px; /* Hide sidebar off-screen */
}
.admin-main-content {
padding: 1rem;
}
/* We'll need a hamburger menu button to toggle the sidebar on mobile */
}
/*--------------------------------------------------------------
# Shopping Cart Page (Dark Theme)
--------------------------------------------------------------*/
.cart-page-wrapper {
padding-top: 4rem;
padding-bottom: 4rem;
}
.cart-item-card {
background: var(--surface-color);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 1.5rem;
margin-bottom: 1.5rem;
transition: box-shadow 0.3s ease;
position: relative;
}
.cart-item-card:hover {
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
}
.cart-item-image img {
border-radius: 8px;
}
.cart-item-details h5 a {
font-weight: 600;
color: var(--heading-color);
text-decoration: none;
transition: color 0.3s ease;
}
.cart-item-details h5 a:hover {
color: var(--primary-color);
}
.quantity-selector {
display: flex;
align-items: center;
background: var(--background-color);
border: 1px solid var(--border-color);
border-radius: 50px;
padding: 5px;
max-width: 120px;
}
.quantity-selector .btn {
background-color: var(--surface-color);
border: 1px solid var(--border-color);
color: var(--text-color);
width: 32px;
height: 32px;
border-radius: 50%;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
.quantity-selector .btn:hover {
background-color: var(--primary-color);
color: var(--heading-color);
border-color: var(--primary-color);
}
.quantity-selector .quantity-input {
border: none;
background: transparent;
font-weight: 700;
color: var(--heading-color);
width: 40px;
text-align: center;
}
.item-price {
font-size: 1.2rem;
font-weight: 700;
color: var(--primary-color);
}
.remove-item-btn {
position: absolute;
top: 15px;
left: 15px;
}
.remove-item-btn .btn {
color: #888;
font-size: 1.2rem;
padding: 0;
transition: color 0.3s ease;
}
.remove-item-btn .btn:hover {
color: #e74c3c;
}
/* Order Summary Card */
.order-summary-card {
background: var(--surface-color);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 2rem;
position: sticky;
top: 120px;
}
.order-summary-card .card-title {
font-weight: 700;
font-size: 1.5rem;
margin-bottom: 2rem;
border-bottom: 1px solid var(--border-color);
padding-bottom: 1rem;
}
.summary-item {
display: flex;
justify-content: space-between;
margin-bottom: 1rem;
font-size: 1rem;
}
.summary-item .label {
color: #aaa;
}
.summary-item .value {
font-weight: 600;
color: var(--text-color);
}
.summary-total {
margin-top: 1.5rem;
padding-top: 1.5rem;
border-top: 1px solid var(--border-color);
}
.summary-total .label {
font-size: 1.2rem;
font-weight: 700;
color: var(--heading-color);
}
.summary-total .value {
font-size: 1.5rem;
font-weight: 700;
color: var(--primary-color);
}
.btn-checkout {
padding: 14px 20px;
font-size: 1.1rem;
font-weight: 700;
border-radius: 8px;
}
/* Empty Cart */
.empty-cart-container {
background: var(--surface-color);
border-radius: 12px;
padding: 4rem;
text-align: center;
border: 1px solid var(--border-color);
}
.empty-cart-container .ri-shopping-cart-line {
font-size: 5rem;
color: var(--primary-color);
opacity: 0.8;
}
.empty-cart-container h2 {
font-weight: 700;
margin-top: 1.5rem;
color: var(--heading-color);
}
.empty-cart-container p {
max-width: 400px;
margin: 1rem auto 2rem auto;
color: #aaa;
}
/*--------------------------------------------------------------
# Checkout Page (Dark Theme)
--------------------------------------------------------------*/
.checkout-form-section {
background: var(--surface-color);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 2.5rem;
}
@media (max-width: 991.98px) {
.checkout-form-section {
padding: 1.5rem;
}
}
.checkout-form-section h4 {
font-weight: 700;
margin-bottom: 2rem;
border-bottom: 1px solid var(--border-color);
padding-bottom: 1rem;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-group .form-control {
background-color: var(--background-color);
border: 1px solid var(--border-color);
color: var(--text-color);
border-radius: 8px;
padding: 12px 20px;
height: 52px;
transition: all 0.3s ease;
}
.form-group .form-control:focus {
background-color: var(--background-color);
color: var(--text-color);
border-color: var(--primary-color);
box-shadow: 0 0 0 0.25rem rgba(192, 160, 128, 0.2);
}
.form-group textarea.form-control {
height: auto;
}
.form-text-info {
font-size: 0.85rem;
font-weight: 500;
color: var(--accent-color) !important;
opacity: 0.9;
}
/* Address Cards */
.address-card-selector .address-card {
background: var(--background-color);
border: 2px solid var(--border-color);
border-radius: 10px;
padding: 1.5rem;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
height: 100%;
}
.address-card-selector .address-card:hover {
border-color: var(--primary-color);
}
.address-card-selector .address-card.selected {
border-color: var(--primary-color);
box-shadow: 0 0 15px rgba(192, 160, 128, 0.3);
}
.address-card-selector .form-check-input {
width: 1.5em;
height: 1.5em;
margin-top: 0.2em;
}
.address-card-title {
font-weight: 600;
color: var(--heading-color);
margin-bottom: 0.5rem;
}
.address-card-detail {
font-size: 0.9rem;
color: var(--text-color);
line-height: 1.6;
}
.address-card-icon {
font-size: 1.5rem;
margin-bottom: 0.5rem;
color: var(--primary-color);
}
#new-address-form-wrapper {
border: 2px dashed var(--border-color);
border-radius: 12px;
padding: 2rem;
margin-top: 1.5rem;
}
/* Checkout Summary */
.checkout-summary-card {
background: var(--surface-color);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 2rem;
position: sticky;
top: 120px;
}
.checkout-summary-card .list-group-item {
background-color: transparent;
border-color: var(--border-color);
color: var(--text-color);
padding-left: 0;
padding-right: 0;
}
.checkout-summary-card .summary-title {
font-weight: 700;
font-size: 1.5rem;
margin-bottom: 1.5rem;
}
.secure-payment-info {
font-size: 0.9rem;
color: #aaa;
}
.secure-payment-info i {
color: #28a745;
}
.form-check-label a {
color: var(--primary-color);
text-decoration: none;
}
.form-check-label a:hover {
text-decoration: underline;
}
/*================================================
# Checkout & Auth Page Dark Theme REFINEMENTS
================================================*/
/* General text & label color fix */
body.bg-dark, .bg-dark, .checkout-form-section, .login-form-section {
/* Make sure all labels and general text are readable */
.form-label, label {
color: #EAEAEA !important; /* Use a bright, readable color */
opacity: 0.9;
font-weight: 500;
margin-bottom: 0.75rem;
}
.text-muted {
color: #a0a0a0 !important; /* A lighter muted color for dark bg */
}
}
/* Improved Form Control Styles */
.form-control.bg-dark, .form-select.bg-dark {
color: var(--text-color) !important;
background-color: #1c1c1c !important; /* Slightly lighter than main bg */
border: 1px solid #4a4a4a;
border-radius: 8px;
height: 50px;
transition: all 0.3s ease;
}
.form-control.bg-dark:focus, .form-select.bg-dark:focus {
background-color: #1c1c1c !important;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(192, 160, 128, 0.25);
}
textarea.form-control.bg-dark {
height: auto;
}
/* Style for the select dropdown arrow */
.form-select.bg-dark {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23d2b48c' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
}
/* Checkbox styles */
.form-check-input {
background-color: #333;
border: 1px solid #555;
}
.form-check-input:checked {
background-color: var(--primary-color);
border-color: var(--primary-color);
}
.form-check-input:focus {
box-shadow: 0 0 0 3px rgba(192, 160, 128, 0.25);
}
.form-check-label {
color: #ccc;
}
.form-check-label a {
color: var(--primary-color);
text-decoration: none;
font-weight: 500;
}
.form-check-label a:hover {
text-decoration: underline;
}
/* Order Summary Card Refinements */
.list-group-item.bg-dark {
border-color: #444 !important; /* Darker border for list items */
}
hr {
border-top-color: #444;
}
.text-primary {
color: var(--primary-color) !important;
}
.btn-primary.btn-lg {
padding-top: 0.9rem;
padding-bottom: 0.9rem;
font-size: 1.1rem;
letter-spacing: 0.5px;
}

View File

@ -1,51 +1,11 @@
// Custom JavaScript will go here
document.addEventListener('DOMContentLoaded', () => {
// --- Theme Toggle Functionality ---
const themeToggleButton = document.getElementById('theme-toggle');
const htmlElement = document.documentElement;
const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');
function applyTheme(theme) {
if (theme === 'dark') {
htmlElement.classList.add('dark');
if (themeToggleButton) themeToggleButton.innerHTML = '<i class="bi bi-sun-fill"></i>';
} else {
htmlElement.classList.remove('dark');
if (themeToggleButton) themeToggleButton.innerHTML = '<i class="bi bi-moon-fill"></i>';
}
}
function initializeTheme() {
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
applyTheme(savedTheme);
} else {
applyTheme(prefersDarkScheme.matches ? 'dark' : 'light');
}
}
if (themeToggleButton) {
themeToggleButton.addEventListener('click', () => {
const newTheme = htmlElement.classList.contains('dark') ? 'light' : 'dark';
localStorage.setItem('theme', newTheme);
applyTheme(newTheme);
});
}
prefersDarkScheme.addEventListener('change', (e) => {
if (!localStorage.getItem('theme')) {
applyTheme(e.matches ? 'dark' : 'light');
}
});
initializeTheme();
// --- AOS Initialization ---
AOS.init({
duration: 800,
once: true,
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

147
auth_handler.php Normal file
View File

@ -0,0 +1,147 @@
<?php
session_start();
require_once __DIR__ . '/db/config.php';
require_once __DIR__ . '/mail/MailService.php';
$action = $_GET['action'] ?? '';
switch ($action) {
case 'send_otp':
handle_send_otp();
break;
case 'verify_otp':
handle_verify_otp();
break;
case 'logout':
handle_logout();
break;
default:
header('Location: index.php');
exit;
}
function handle_send_otp() {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: login.php');
exit;
}
$email = filter_var(trim($_POST['email'] ?? ''), FILTER_VALIDATE_EMAIL);
if (!$email) {
flash_message('danger', 'لطفاً یک آدرس ایمیل معتبر وارد کنید.', 'login.php');
}
try {
$pdo = db();
// Generate a secure random code
$otp_code = random_int(100000, 999999);
$code_hash = password_hash((string)$otp_code, PASSWORD_DEFAULT);
// OTP is valid for 10 minutes
$expires_at = date('Y-m-d H:i:s', time() + (10 * 60));
// Store the hashed code in the database
$stmt = $pdo->prepare("INSERT INTO otp_codes (email, code_hash, expires_at) VALUES (?, ?, ?)");
$stmt->execute([$email, $code_hash, $expires_at]);
// Send the plain code to the user's email
$subject = "کد ورود شما به فروشگاه آتیمه";
$body = "<div dir='rtl' style='font-family: Vazirmatn, sans-serif; text-align: right;'><h2>کد تایید شما</h2><p>برای ورود یا ثبت‌نام در وب‌سایت آتیمه، از کد زیر استفاده کنید:</p><p style='font-size: 24px; font-weight: bold; letter-spacing: 5px; text-align: center; background: #f0f0f0; padding: 10px; border-radius: 8px;'>{$otp_code}</p><p>این کد تا ۱۰ دقیقه دیگر معتبر است.</p></div>";
$mail_result = MailService::sendMail($email, $subject, $body);
if (!$mail_result['success']) {
error_log('OTP Mail Error: ' . ($mail_result['error'] ?? 'Unknown error'));
flash_message('danger', 'خطایی در ارسال ایمیل رخ داد. لطفاً مطمئن شوید ایمیل را درست وارد کرده‌اید.', 'login.php');
}
// Store email in session to use on the verification page
$_SESSION['otp_email'] = $email;
header('Location: verify.php');
exit;
} catch (Exception $e) {
error_log('OTP Generation Error: ' . $e->getMessage());
flash_message('danger', 'خطای سرور. لطفاً لحظاتی دیگر دوباره تلاش کنید.', 'login.php');
}
}
function handle_verify_otp() {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: login.php');
exit;
}
$email = filter_var(trim($_POST['email'] ?? ''), FILTER_VALIDATE_EMAIL);
$otp_code = trim($_POST['otp_code'] ?? '');
if (!$email || !$otp_code) {
flash_message('danger', 'ایمیل یا کد تایید نامعتبر است.', 'login.php');
}
try {
$pdo = db();
// Find the latest, unused OTP for this email that has not expired
$stmt = $pdo->prepare("SELECT * FROM otp_codes WHERE email = ? AND is_used = 0 AND expires_at > NOW() ORDER BY created_at DESC LIMIT 1");
$stmt->execute([$email]);
$otp_row = $stmt->fetch(PDO::FETCH_ASSOC);
if ($otp_row && password_verify($otp_code, $otp_row['code_hash'])) {
// Mark OTP as used
$stmt_update = $pdo->prepare("UPDATE otp_codes SET is_used = 1 WHERE id = ?");
$stmt_update->execute([$otp_row['id']]);
// Check if user exists
$stmt_user = $pdo->prepare("SELECT * FROM users WHERE email = ?");
$stmt_user->execute([$email]);
$user = $stmt_user->fetch(PDO::FETCH_ASSOC);
$user_id = null;
if ($user) {
// User exists, log them in
$user_id = $user['id'];
$_SESSION['user_name'] = $user['first_name']; // Might be null, that's ok
} else {
// User does not exist, create a new one
$stmt_create = $pdo->prepare("INSERT INTO users (email, is_admin) VALUES (?, 0)");
$stmt_create->execute([$email]);
$user_id = $pdo->lastInsertId();
$_SESSION['user_name'] = null; // New user has no name yet
}
// Set session variables for login
$_SESSION['user_id'] = $user_id;
unset($_SESSION['otp_email']); // Clean up session
// Redirect to homepage with success
flash_message('success', 'شما با موفقیت وارد شدید!', 'index.php');
} else {
// Invalid or expired OTP
flash_message('danger', 'کد وارد شده اشتباه یا منقضی شده است.', 'verify.php');
}
} catch (Exception $e) {
error_log('OTP Verification Error: ' . $e->getMessage());
flash_message('danger', 'خطای سرور. لطفاً لحظاتی دیگر دوباره تلاش کنید.', 'verify.php');
}
}
function handle_logout() {
session_unset();
session_destroy();
header('Location: index.php');
exit;
}
function flash_message($type, $message, $location) {
// Ensure email is carried over to verify page on error
if ($location === 'verify.php' && isset($_POST['email'])) {
$_SESSION['otp_email'] = $_POST['email'];
}
$_SESSION['flash_message'] = ['type' => $type, 'message' => $message];
header("Location: $location");
exit;
}

211
cart.php
View File

@ -1,121 +1,104 @@
<?php
session_start();
require_once 'db/config.php';
$cart_items_detailed = [];
$total_price = 0;
if (!empty($_SESSION['cart'])) {
$cart_item_ids = array_keys($_SESSION['cart']);
// Extract pure product IDs from the composite key (e.g., '1-Black' -> '1')
$product_ids = array_map(function($id) {
return (int)explode('-', $id)[0];
}, $cart_item_ids);
if (!empty($product_ids)) {
$placeholders = implode(',', array_fill(0, count($product_ids), '?'));
try {
$pdo = db();
$stmt = $pdo->prepare("SELECT id, name, price, image_url FROM products WHERE id IN ($placeholders)");
$stmt->execute(array_unique($product_ids));
$products_data = $stmt->fetchAll(PDO::FETCH_ASSOC | PDO::FETCH_UNIQUE);
foreach ($_SESSION['cart'] as $cart_item_id => $item) {
$product_id = (int)explode('-', $cart_item_id)[0];
if (isset($products_data[$product_id])) {
$product = $products_data[$product_id];
$quantity = $item['quantity'];
$color = $item['color'];
$subtotal = $product['price'] * $quantity;
$total_price += $subtotal;
$cart_items_detailed[] = [
'cart_item_id' => $cart_item_id,
'product_id' => $product_id,
'name' => $product['name'],
'price' => $product['price'],
'image_url' => $product['image_url'],
'quantity' => $quantity,
'color' => $color,
'subtotal' => $subtotal
];
}
}
} catch (PDOException $e) {
error_log("DB Error: " . $e->getMessage());
$cart_items_detailed = [];
$total_price = 0;
}
}
}
$page_title = 'سبد خرید';
include 'includes/header.php';
require_once 'includes/header.php';
$cart_items = $_SESSION['cart'] ?? [];
$total_price = 0;
?>
<div class="text-center mb-5">
<h1 class="display-4 fw-bold">سبد خرید شما</h1>
<div class="cart-page-wrapper">
<div class="container">
<?php if (empty($cart_items)): ?>
<div class="empty-cart-container">
<i class="ri-shopping-cart-line"></i>
<h2>سبد خرید شما خالی است</h2>
<p>به نظر می‌رسد هنوز محصولی به سبد خرید خود اضافه نکرده‌اید. همین حالا گشتی در فروشگاه بزنید.</p>
<a href="shop.php" class="btn btn-primary btn-lg btn-checkout">
<i class="ri-store-2-line me-2"></i>
رفتن به فروشگاه
</a>
</div>
<?php else: ?>
<div class="text-center mb-5">
<h1 class="fw-bold display-5">سبد خرید شما</h1>
<p class="text-white-50 fs-5">جزئیات سفارش خود را بررسی و نهایی کنید.</p>
</div>
<div class="row g-5">
<div class="col-lg-8">
<?php foreach ($cart_items as $item_id => $item):
$item_total = $item['price'] * $item['quantity'];
$total_price += $item_total;
?>
<div class="cart-item-card">
<div class="remove-item-btn">
<form action="cart_handler.php" method="POST" class="d-inline">
<input type="hidden" name="product_id" value="<?php echo $item['product_id']; ?>">
<input type="hidden" name="product_color" value="<?php echo htmlspecialchars($item['color'] ?? ''); ?>">
<input type="hidden" name="action" value="remove">
<button type="submit" class="btn btn-link text-decoration-none p-0"><i class="ri-close-circle-line"></i></button>
</form>
</div>
<div class="row align-items-center g-3">
<div class="col-md-2 col-3 cart-item-image">
<a href="product.php?id=<?php echo $item['product_id']; ?>">
<img src="<?php echo htmlspecialchars($item['image_url']); ?>" class="img-fluid" alt="<?php echo htmlspecialchars($item['name']); ?>">
</a>
</div>
<div class="col-md-4 col-9 cart-item-details">
<h5><a href="product.php?id=<?php echo $item['product_id']; ?>"><?php echo htmlspecialchars($item['name']); ?></a></h5>
<?php if (!empty($item['color'])): ?>
<div class="d-flex align-items-center">
<small class="text-white-50 me-2">رنگ:</small>
<span class="d-inline-block rounded-circle border" style="width: 20px; height: 20px; background-color: <?php echo htmlspecialchars($item['color']); ?>;"></span>
</div>
<?php endif; ?>
</div>
<div class="col-md-3 col-7">
<form action="cart_handler.php" method="POST" class="quantity-selector">
<input type="hidden" name="product_id" value="<?php echo $item['product_id']; ?>">
<input type="hidden" name="product_color" value="<?php echo htmlspecialchars($item['color'] ?? ''); ?>">
<input type="hidden" name="action" value="update">
<button type="submit" name="quantity" value="<?php echo $item['quantity'] + 1; ?>" class="btn">+</button>
<input type="text" value="<?php echo $item['quantity']; ?>" class="quantity-input" readonly>
<button type="submit" name="quantity" value="<?php echo $item['quantity'] - 1; ?>" class="btn" <?php echo $item['quantity'] <= 1 ? 'disabled' : ''; ?>>-</button>
</form>
</div>
<div class="col-md-3 col-5 text-end">
<span class="item-price"><?php echo number_format($item_total); ?> <small>تومان</small></span>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<div class="col-lg-4">
<div class="order-summary-card">
<h4 class="card-title">خلاصه سفارش</h4>
<div class="summary-item">
<span class="label">جمع کل</span>
<span class="value"><?php echo number_format($total_price); ?> تومان</span>
</div>
<div class="summary-item">
<span class="label">هزینه ارسال</span>
<span class="value text-success">رایگان</span>
</div>
<div class="summary-total">
<div class="summary-item">
<span class="label">مبلغ نهایی</span>
<span class="value"><?php echo number_format($total_price); ?> تومان</span>
</div>
</div>
<div class="d-grid mt-4">
<a href="checkout.php" class="btn btn-primary btn-lg btn-checkout">ادامه و پرداخت</a>
</div>
</div>
</div>
</div>
<?php endif; ?>
</div>
</div>
<?php if (empty($cart_items_detailed)): ?>
<div class="text-center p-5 bg-light rounded-3">
<p class="lead">سبد خرید شما خالی است.</p>
<a href="shop.php" class="btn btn-primary">بازگشت به فروشگاه</a>
</div>
<?php else: ?>
<form action="cart_handler.php" method="POST">
<input type="hidden" name="update_cart" value="1">
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead class="table-light">
<tr>
<th scope="col" colspan="2">محصول</th>
<th scope="col" class="text-center">قیمت</th>
<th scope="col" class="text-center">تعداد</th>
<th scope="col" class="text-end">جمع کل</th>
<th scope="col" class="text-center">حذف</th>
</tr>
</thead>
<tbody>
<?php foreach ($cart_items_detailed as $item): ?>
<tr>
<td style="width: 100px;">
<img src="<?php echo htmlspecialchars($item['image_url']); ?>" alt="<?php echo htmlspecialchars($item['name']); ?>" class="img-fluid rounded-3">
</td>
<td>
<h5 class="mb-0"><?php echo htmlspecialchars($item['name']); ?></h5>
<?php if ($item['color']): ?>
<small class="text-muted">رنگ: <?php echo htmlspecialchars($item['color']); ?></small>
<?php endif; ?>
</td>
<td class="text-center"><strong><?php echo number_format($item['price']); ?></strong></td>
<td class="text-center" style="width: 120px;">
<input type="number" class="form-control text-center" name="quantities[<?php echo $item['cart_item_id']; ?>]" value="<?php echo $item['quantity']; ?>" min="1" max="10">
</td>
<td class="text-end"><strong><?php echo number_format($item['subtotal']); ?></strong></td>
<td class="text-center">
<a href="cart_handler.php?action=remove&id=<?php echo $item['cart_item_id']; ?>" class="btn btn-sm btn-outline-danger">&times;</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div class="d-flex justify-content-between align-items-center mt-4 flex-wrap gap-3">
<button type="submit" class="btn btn-outline-secondary">به‌روزرسانی سبد</button>
<div class="text-end">
<h4>جمع نهایی: <span class="fw-bold text-primary"><?php echo number_format($total_price); ?> تومان</span></h4>
</div>
</div>
</form>
<div class="text-center mt-5">
<a href="checkout.php" class="btn btn-primary btn-lg">ادامه جهت تسویه حساب</a>
</div>
<?php endif; ?>
<?php include 'includes/footer.php'; ?>
<?php require_once 'includes/footer.php'; ?>

View File

@ -1,70 +1,77 @@
<?php
session_start();
require_once 'db/config.php';
// Initialize the cart if it doesn't exist
// Initialize cart if it doesn't exist
if (!isset($_SESSION['cart'])) {
$_SESSION['cart'] = [];
}
// Check if the form was submitted and it's an add-to-cart action
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_to_cart'])) {
$product_id = isset($_POST['product_id']) ? (int)$_POST['product_id'] : 0;
$quantity = isset($_POST['quantity']) ? (int)$_POST['quantity'] : 1;
$color = isset($_POST['color']) ? trim($_POST['color']) : null;
// Get POST data
$product_id = filter_input(INPUT_POST, 'product_id', FILTER_VALIDATE_INT);
$quantity = filter_input(INPUT_POST, 'quantity', FILTER_VALIDATE_INT);
$color = filter_input(INPUT_POST, 'product_color', FILTER_SANITIZE_STRING);
$action = filter_input(INPUT_POST, 'action', FILTER_SANITIZE_STRING);
if ($product_id > 0 && $quantity > 0) {
// Create a unique ID for the cart item based on product ID and color
$cart_item_id = $product_id . ($color ? '-' . preg_replace('/[^a-zA-Z0-9_]/ ', '-', $color) : '');
// If the exact item (product + color) is already in the cart, update the quantity
if (isset($_SESSION['cart'][$cart_item_id])) {
$_SESSION['cart'][$cart_item_id]['quantity'] += $quantity;
} else {
// Otherwise, add it as a new item
$_SESSION['cart'][$cart_item_id] = [
'product_id' => $product_id,
'quantity' => $quantity,
'color' => $color
];
}
}
// Redirect to the cart page to show the updated cart
header('Location: cart.php');
if (!$action || !$product_id) {
header('Location: shop.php');
exit;
}
// Handle removing an item from the cart
if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['action']) && $_GET['action'] === 'remove') {
$cart_item_id = isset($_GET['id']) ? $_GET['id'] : '';
if (!empty($cart_item_id) && isset($_SESSION['cart'][$cart_item_id])) {
unset($_SESSION['cart'][$cart_item_id]);
}
// Redirect back to the cart page
header('Location: cart.php');
exit;
}
// Generate a unique ID for the cart item based on product ID and color
$cart_item_id = $product_id . ($color ? '_' . str_replace('#', '', $color) : '');
// Handle updating quantities
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['update_cart'])){
if(!empty($_POST['quantities'])){
foreach($_POST['quantities'] as $cart_item_id => $quantity){
$quantity = (int)$quantity;
if(!empty($cart_item_id) && isset($_SESSION['cart'][$cart_item_id])){
if($quantity > 0){
$_SESSION['cart'][$cart_item_id]['quantity'] = $quantity;
} else {
// Remove item if quantity is 0 or less
unset($_SESSION['cart'][$cart_item_id]);
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 = ?");
$stmt->execute([$product_id]);
$product = $stmt->fetch(PDO::FETCH_ASSOC);
if ($product) {
// If item already in cart, update quantity
if (isset($_SESSION['cart'][$cart_item_id])) {
$_SESSION['cart'][$cart_item_id]['quantity'] += $quantity;
} else {
// Otherwise, add new item
$_SESSION['cart'][$cart_item_id] = [
'product_id' => $product_id,
'name' => $product['name'],
'price' => $product['price'],
'image_url' => $product['image_url'],
'quantity' => $quantity,
'color' => $color
];
}
}
} catch (PDOException $e) {
// Log error, maybe set a session error message to display in cart
error_log("Cart Add Error: " . $e->getMessage());
}
}
}
header('Location: cart.php');
exit;
break;
case 'update':
if ($quantity > 0) {
if (isset($_SESSION['cart'][$cart_item_id])) {
$_SESSION['cart'][$cart_item_id]['quantity'] = $quantity;
}
} else {
// If quantity is 0 or less, remove the item
unset($_SESSION['cart'][$cart_item_id]);
}
break;
case 'remove':
if (isset($_SESSION['cart'][$cart_item_id])) {
unset($_SESSION['cart'][$cart_item_id]);
}
break;
}
// If someone accesses this file directly without a valid action, redirect them to the shop.
header('Location: shop.php');
exit;
// Redirect back to the cart page to show changes
header('Location: cart.php');
exit;

View File

@ -2,173 +2,194 @@
session_start();
require_once 'db/config.php';
// If cart is empty, redirect to shop page, there is nothing to checkout
// Redirect if cart is empty
if (empty($_SESSION['cart'])) {
header('Location: shop.php');
exit;
}
$p_title = "تسویه حساب";
$order_placed_successfully = false;
$error_message = '';
$cart_items = $_SESSION['cart'];
$total_price = array_reduce($cart_items, function ($sum, $item) {
return $sum + ($item['price'] * $item['quantity']);
}, 0);
// Handle form submission
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// --- Data Validation ---
$name = trim($_POST['customer_name'] ?? '');
$email = trim($_POST['customer_email'] ?? '');
$address = trim($_POST['customer_address'] ?? '');
// User and address data
$logged_in_user = null;
$user_addresses = [];
$is_logged_in = isset($_SESSION['user_id']);
if (empty($name) || empty($email) || empty($address)) {
$error_message = 'لطفاً تمام فیلدها را پر کنید.';
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$error_message = 'لطفاً یک آدرس ایمیل معتبر وارد کنید.';
}
if(empty($error_message)) {
if ($is_logged_in) {
try {
$pdo = db();
try {
// --- Server-side recalculation of total ---
$product_ids = array_keys($_SESSION['cart']);
$placeholders = implode(',', array_fill(0, count($product_ids), '?'));
$stmt = $pdo->prepare("SELECT id, price FROM products WHERE id IN ($placeholders)");
$stmt->execute($product_ids);
$products_from_db = $stmt->fetchAll(PDO::FETCH_ASSOC | PDO::FETCH_UNIQUE);
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$_SESSION['user_id']]);
$logged_in_user = $stmt->fetch(PDO::FETCH_ASSOC);
$total_amount = 0;
foreach ($_SESSION['cart'] as $product_id => $quantity) {
if(isset($products_from_db[$product_id])){
$total_amount += $products_from_db[$product_id]['price'] * $quantity;
}
}
// --- Database Transaction ---
$pdo->beginTransaction();
// 1. Insert into orders table
$sql_order = "INSERT INTO orders (customer_name, customer_email, customer_address, total_amount) VALUES (?, ?, ?, ?)";
$stmt_order = $pdo->prepare($sql_order);
$stmt_order->execute([$name, $email, $address, $total_amount]);
$order_id = $pdo->lastInsertId();
// 2. Insert into order_items table
$sql_items = "INSERT INTO order_items (order_id, product_id, quantity, price) VALUES (?, ?, ?, ?)";
$stmt_items = $pdo->prepare($sql_items);
foreach ($_SESSION['cart'] as $product_id => $quantity) {
if(isset($products_from_db[$product_id])){
$price = $products_from_db[$product_id]['price'];
$stmt_items->execute([$order_id, $product_id, $quantity, $price]);
}
}
// 3. Commit the transaction
$pdo->commit();
// 4. Clear the cart and set success flag
unset($_SESSION['cart']);
$order_placed_successfully = true;
$p_title = "سفارش شما ثبت شد";
} catch (Exception $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
error_log("Checkout Error: " . $e->getMessage());
$error_message = 'مشکلی در ثبت سفارش شما به وجود آمد. لطفاً دوباره تلاش کنید.';
}
$stmt = $pdo->prepare("SELECT * FROM user_addresses WHERE user_id = ? ORDER BY is_default DESC, id DESC");
$stmt->execute([$_SESSION['user_id']]);
$user_addresses = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
// In a real app, log this error
die("Error fetching user data.");
}
}
$page_title = 'تکمیل سفارش';
require_once 'includes/header.php';
?>
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo $p_title; ?> - چرم آتیمه</title>
<meta name="robots" content="noindex, nofollow">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700&family=Inter:wght@400;500&family=Lalezar&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body class="bg-dark text-white">
<!-- Header -->
<header class="p-3 mb-3 border-bottom border-secondary">
<div class="container">
<div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
<a href="index.php" class="d-flex align-items-center mb-2 mb-lg-0 text-white text-decoration-none">
<h1 class="font-playfair fs-2" style="color: #D4AF37;">آتیمه</h1>
</a>
<ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0">
<li><a href="index.php" class="nav-link px-2 text-white">خانه</a></li>
<li><a href="shop.php" class="nav-link px-2 text-white">فروشگاه</a></li>
</ul>
<div class="text-end">
<a href="cart.php" class="btn btn-outline-warning position-relative">
سبد خرید
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
<?php echo count($_SESSION['cart'] ?? []); ?>
</span>
</a>
<div class="container my-5 bg-dark text-light">
<?php
if (isset($_SESSION['error_message'])) {
echo '<div class="alert alert-danger alert-dismissible fade show" role="alert">' . htmlspecialchars($_SESSION['error_message']) . '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>';
unset($_SESSION['error_message']);
}
?>
<div class="text-center mb-5">
<h1 class="fw-bold">تکمیل فرآیند خرید</h1>
<p class="text-muted">فقط یک قدم دیگر تا نهایی شدن سفارش شما باقیست.</p>
</div>
<form action="checkout_handler.php" method="POST">
<div class="row g-5">
<!-- Shipping Details -->
<div class="col-lg-7">
<h3 class="mb-4">اطلاعات ارسال</h3>
<?php if ($is_logged_in && !empty($user_addresses)): ?>
<div class="mb-4">
<label for="saved_address" class="form-label">انتخاب آدرس</label>
<select class="form-select bg-dark text-light" id="saved_address">
<option value="">یک آدرس انتخاب کنید یا فرم زیر را پر کنید...</option>
<?php foreach ($user_addresses as $addr): ?>
<option value='<?php echo json_encode($addr, JSON_HEX_APOS | JSON_HEX_QUOT); ?>'>
<?php echo htmlspecialchars($addr['province'] . '، ' . $addr['city'] . '، ' . $addr['address_line']); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?php endif; ?>
<div class="card bg-dark border-secondary shadow-sm rounded-4">
<div class="card-body p-4">
<div class="row g-3">
<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>
<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>
<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>
<?php if (!$is_logged_in): ?>
<div class="form-text text-info fw-bold">توجه: فقط با شماره تلفن همراه میتوان سفارش را رهگیری کرد.</div>
<?php endif; ?>
</div>
<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>
<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>
<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>
<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>
<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>
</div>
<div class="form-check mt-4">
<input type="checkbox" class="form-check-input" id="terms" name="terms" required>
<label class="form-check-label" for="terms">
با <a href="#">قوانین و مقررات</a> سایت موافقم.
</label>
</div>
</div>
</div>
</div>
<!-- Order Summary -->
<div class="col-lg-5">
<div class="card bg-dark border-secondary shadow-sm rounded-4 sticky-top" style="top: 100px;">
<div class="card-body p-4">
<h4 class="card-title fw-bold mb-4">خلاصه سفارش</h4>
<ul class="list-group list-group-flush mb-4">
<?php foreach($cart_items as $item): ?>
<li class="list-group-item bg-dark text-light d-flex justify-content-between align-items-center px-0">
<div class="d-flex align-items-center">
<img src="<?php echo htmlspecialchars($item['image_url']); ?>" width="60" class="rounded-3 me-3" alt="<?php echo htmlspecialchars($item['name']); ?>">
<div>
<?php echo htmlspecialchars($item['name']); ?>
<small class="d-block text-muted">تعداد: <?php echo $item['quantity']; ?></small>
</div>
</div>
<span class="fw-bold"><?php echo number_format($item['price'] * $item['quantity']); ?></span>
</li>
<?php endforeach; ?>
</ul>
<div class="d-flex justify-content-between mb-2">
<span class="text-muted">جمع کل</span>
<span><?php echo number_format($total_price); ?> تومان</span>
</div>
<div class="d-flex justify-content-between mb-3">
<span class="text-muted">هزینه ارسال</span>
<span class="text-success">رایگان</span>
</div>
<hr class="border-secondary">
<div class="d-flex justify-content-between align-items-center mb-4">
<span class="h5 fw-bold">مبلغ نهایی</span>
<span class="h5 fw-bolder text-primary"><?php echo number_format($total_price); ?> تومان</span>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary btn-lg">ثبت نهایی سفارش</button>
</div>
<div class="text-center mt-3">
<small class="text-muted"><i class="ri-lock-line me-1"></i> پرداخت امن و مطمئن</small>
</div>
</div>
</div>
</div>
</div>
</header>
</form>
</div>
<!-- Main Content -->
<main class="container my-5">
<div class="text-center mb-5">
<h2 class="font-lalezar display-4"><?php echo $p_title; ?></h2>
</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>
<div class="row justify-content-center">
<div class="col-md-8">
<?php if ($order_placed_successfully): ?>
<div class="alert alert-success text-center">
<h4>از خرید شما متشکریم!</h4>
<p>سفارش شما با موفقیت ثبت شد و به زودی پردازش خواهد شد. یک ایمیل تایید برای شما ارسال گردید.</p>
<a href="shop.php" class="btn btn-warning">بازگشت به فروشگاه</a>
</div>
<?php else: ?>
<?php if (!empty($error_message)): ?>
<div class="alert alert-danger">.<?php echo $error_message; ?></div>
<?php endif; ?>
<div class="card bg-dark-2">
<div class="card-body p-4">
<h5 class="card-title mb-4">اطلاعات ارسال</h5>
<form action="checkout.php" method="POST">
<div class="mb-3">
<label for="customer_name" class="form-label">نام و نام خانوادگی</label>
<input type="text" class="form-control bg-dark text-white" id="customer_name" name="customer_name" required>
</div>
<div class="mb-3">
<label for="customer_email" class="form-label">آدرس ایمیل</label>
<input type="email" class="form-control bg-dark text-white" id="customer_email" name="customer_email" required>
</div>
<div class="mb-3">
<label for="customer_address" class="form-label">آدرس کامل</label>
<textarea class="form-control bg-dark text-white" id="customer_address" name="customer_address" rows="3" required></textarea>
</div>
<div class="d-grid mt-4">
<button type="submit" class="btn btn-warning btn-lg fw-bold">ثبت سفارش و پرداخت</button>
</div>
</form>
</div>
</div>
<?php endif; ?>
</div>
</div>
</main>
<!-- Footer -->
<footer class="py-5 mt-5 border-top border-secondary">
<div class="container text-center">
<p class="text-muted">&copy; <?php echo date("Y"); ?> چرم آتیمه. تمام حقوق محفوظ است.</p>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
<?php require_once 'includes/footer.php'; ?>

100
checkout_handler.php Normal file
View File

@ -0,0 +1,100 @@
<?php
session_start();
require_once 'db/config.php';
// 1. Basic Security: Only allow POST requests and check for cart
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: index.php');
exit;
}
if (empty($_SESSION['cart'])) {
header('Location: shop.php');
exit;
}
// 2. Retrieve and sanitize form data
$first_name = filter_input(INPUT_POST, 'first_name', FILTER_SANITIZE_STRING);
$last_name = filter_input(INPUT_POST, 'last_name', FILTER_SANITIZE_STRING);
$phone_number = filter_input(INPUT_POST, 'phone_number', FILTER_SANITIZE_STRING);
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
$province = filter_input(INPUT_POST, 'province', FILTER_SANITIZE_STRING);
$city = filter_input(INPUT_POST, 'city', FILTER_SANITIZE_STRING);
$address_line = filter_input(INPUT_POST, 'address_line', FILTER_SANITIZE_STRING);
$postal_code = filter_input(INPUT_POST, 'postal_code', FILTER_SANITIZE_STRING);
// 3. Server-side validation (Email is now optional)
if (!$first_name || !$last_name || !$phone_number || !$province || !$city || !$address_line || !$postal_code) {
$_SESSION['error_message'] = 'لطفاً تمام فیلدهای آدرس به جز ایمیل را تکمیل کنید.';
header('Location: checkout.php');
exit;
}
$pdo = db();
$pdo->beginTransaction();
try {
// 4. Prepare order data
$billing_name = trim($first_name . ' ' . $last_name);
$cart_items = $_SESSION['cart'];
$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);
// 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
);
$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
$stmt->execute([
$user_id,
$billing_name,
$final_email,
$phone_number,
$province,
$city,
$address_line,
$postal_code,
$total_amount,
$items_json,
$status
]);
// 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 = ?");
$stmt_check_addr->execute([$user_id, $address_line, $postal_code]);
$address_exists = $stmt_check_addr->fetchColumn();
if ($address_exists == 0) {
$stmt_save_addr = $pdo->prepare(
"INSERT INTO user_addresses (user_id, first_name, last_name, phone_number, province, city, address_line, postal_code) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
);
$stmt_save_addr->execute([
$user_id, $first_name, $last_name, $phone_number, $province, $city, $address_line, $postal_code
]);
}
}
// 7. Commit transaction
$pdo->commit();
// 8. Clear the cart and redirect with a success message
unset($_SESSION['cart']);
$_SESSION['success_message'] = 'سفارش شما با موفقیت ثبت شد! از خرید شما متشکریم.';
header('Location: index.php');
exit;
} catch (Exception $e) {
// 9. If anything fails, rollback and redirect with an error
$pdo->rollBack();
error_log("Order Creation Failed: " . $e->getMessage()); // Log error for admin
$_SESSION['error_message'] = 'خطایی در ثبت سفارش رخ داد. لطفاً دوباره تلاش کنید.';
header('Location: checkout.php');
exit;
}

View File

@ -1,73 +1,69 @@
<?php
session_start();
require_once __DIR__ . '/mail/MailService.php';
session_start(); // Ensure session is started
$page_title = 'تماس با ما';
$page_description = 'با ما در تماس باشید. نظرات و پیشنهادات شما برای ما ارزشمند است.';
require_once 'includes/header.php';
require_once 'mail/MailService.php';
$message = '';
$error = '';
// Handle form submission
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST['contact_form'])) {
$name = trim(filter_input(INPUT_POST, 'name', FILTER_SANITIZE_STRING));
$email = trim(filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL));
$message = trim(filter_input(INPUT_POST, 'message', FILTER_SANITIZE_STRING));
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$name = trim($_POST['name']);
$email = trim($_POST['email']);
$subject = trim($_POST['subject']);
$body = trim($_POST['message']);
if (empty($name) || empty($email) || empty($subject) || empty($body) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
$error = 'لطفاً تمام فیلدها را به درستی پر کنید.';
if (empty($name) || empty($email) || empty($message)) {
$_SESSION['flash_message'] = ['type' => 'error', 'message' => 'لطفاً تمام فیلدها را پر کنید.'];
} elseif (!$email) {
$_SESSION['flash_message'] = ['type' => 'error', 'message' => 'آدرس ایمیل وارد شده معتبر نیست.'];
} else {
$response = MailService::sendContactMessage($name, $email, $body, null, $subject);
if (!empty($response['success'])) {
$message = 'پیام شما با موفقیت ارسال شد. سپاسگزاریم!';
// Send email using MailService
$to_email = getenv('MAIL_TO') ?: 'your-default-email@example.com'; // Fallback email
$subject = "پیام جدید از فرم تماس وب‌سایت";
$email_result = MailService::sendContactMessage($name, $email, $message, $to_email, $subject);
if (!empty($email_result['success'])) {
$_SESSION['flash_message'] = ['type' => 'success', 'message' => 'پیام شما با موفقیت ارسال شد. سپاسگزاریم!'];
} else {
$error = 'خطایی در ارسال پیام رخ داد. لطفاً بعداً تلاش کنید. متن خطا: ' . htmlspecialchars($response['error'] ?? 'Unknown error');
$_SESSION['flash_message'] = ['type' => 'error', 'message' => 'خطا در ارسال پیام. لطفاً بعداً دوباره تلاش کنید.'];
error_log("MailService Error: " . ($email_result['error'] ?? 'Unknown error'));
}
}
// Redirect to the same page to prevent form resubmission
header("Location: contact.php");
exit();
}
include 'includes/header.php';
// 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 text-center" data-aos="fade-up">
<h1 class="display-4 fw-bold"><?php echo $page_title; ?></h1>
<p class="lead text-white-50 mt-3"><?php echo $page_description; ?></p>
</div>
</div>
<div class="row justify-content-center mt-5">
<div class="col-lg-8">
<div class="card border-0" style="background-color: var(--surface-color);">
<div class="card-body p-4 p-md-5">
<?php if ($message): ?>
<div class="alert alert-success" role="alert">
<?php echo $message; ?>
</div>
<?php endif; ?>
<?php if ($error): ?>
<div class="alert alert-danger" role="alert">
<?php echo $error; ?>
</div>
<?php endif; ?>
<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>
<form action="contact.php" method="POST" data-aos="fade-up" data-aos-delay="200">
<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 text-white" id="name" name="name" required>
<input type="text" class="form-control form-control-lg bg-dark" id="name" name="name" required>
</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 text-white" id="email" name="email" required>
</div>
<div class="mb-4">
<label for="subject" class="form-label fs-5">موضوع</label>
<input type="text" class="form-control form-control-lg bg-dark text-white" id="subject" name="subject" required>
<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="mb-4">
<label for="message" class="form-label fs-5">پیام شما</label>
<textarea class="form-control form-control-lg bg-dark text-white" id="message" name="message" rows="5" required></textarea>
<textarea class="form-control form-control-lg bg-dark" id="message" name="message" rows="6" required></textarea>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary btn-lg">ارسال پیام</button>
@ -75,8 +71,24 @@ include 'includes/header.php';
</form>
</div>
</div>
<div class="alert alert-info mt-4"><b>توجه:</b> این فرم برای اهداف آزمایشی است. برای دریافت واقعی ایمیل‌ها، باید اطلاعات سرور ایمیل (SMTP) خود را در فایل <code>.env</code> وارد کنید.</div>
</div>
</div>
</main>
<?php include 'includes/footer.php'; ?>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
<?php if ($flash_message): ?>
Swal.fire({
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'
});
<?php endif; ?>
});
</script>
<?php require_once 'includes/footer.php'; ?>

View File

@ -1 +1,2 @@
ALTER TABLE products ADD COLUMN colors VARCHAR(255) DEFAULT NULL COMMENT 'Comma-separated list of available colors';
-- Add the colors column to the products table if it doesn't exist
ALTER TABLE `products` ADD COLUMN IF NOT EXISTS `colors` VARCHAR(255) DEFAULT NULL COMMENT 'Comma-separated list of available colors';

View File

@ -1 +1,2 @@
ALTER TABLE products ADD COLUMN is_featured BOOLEAN DEFAULT 0;
-- Add the is_featured column to the products table if it doesn't exist
ALTER TABLE `products` ADD COLUMN IF NOT EXISTS `is_featured` BOOLEAN DEFAULT 0;

View File

@ -0,0 +1,13 @@
CREATE TABLE IF NOT EXISTS `orders` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`customer_name` VARCHAR(255) NOT NULL,
`customer_email` VARCHAR(255) NOT NULL,
`customer_address` TEXT NOT NULL,
`customer_phone` VARCHAR(50) DEFAULT NULL,
`total_amount` DECIMAL(10, 2) NOT NULL,
`items_json` JSON NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Add the status column to the orders table if it doesn't exist
ALTER TABLE `orders` ADD COLUMN IF NOT EXISTS `status` VARCHAR(50) NOT NULL DEFAULT 'Pending' COMMENT 'e.g., Pending, Processing, Shipped, Delivered, Canceled';

View File

@ -0,0 +1,11 @@
-- Create users table to store customer information
CREATE TABLE IF NOT EXISTS `users` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`first_name` VARCHAR(100) NOT NULL,
`last_name` VARCHAR(100) NOT NULL,
`email` VARCHAR(150) NOT NULL UNIQUE,
`phone_number` VARCHAR(20) DEFAULT NULL UNIQUE,
`password` VARCHAR(255) NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

View File

@ -0,0 +1,12 @@
-- Create user_addresses table to store customer shipping addresses
CREATE TABLE IF NOT EXISTS `user_addresses` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`user_id` INT NOT NULL,
`province` VARCHAR(100) NOT NULL,
`city` VARCHAR(100) NOT NULL,
`address_line` TEXT NOT NULL,
`postal_code` VARCHAR(20) NOT NULL,
`is_default` BOOLEAN DEFAULT FALSE,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

View File

@ -0,0 +1,2 @@
-- Add is_admin flag to users table to differentiate admins from regular users
ALTER TABLE `users` ADD `is_admin` BOOLEAN NOT NULL DEFAULT FALSE AFTER `password`;

View File

@ -0,0 +1,18 @@
-- Update orders table to support structured addresses and link to users
-- Add user_id to link orders to the users table (can be NULL for guest checkouts)
ALTER TABLE `orders` ADD COLUMN `user_id` INT NULL DEFAULT NULL AFTER `id`, ADD CONSTRAINT `fk_orders_users` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL;
-- Add structured shipping address fields
ALTER TABLE `orders`
ADD COLUMN `shipping_province` VARCHAR(100) NOT NULL AFTER `customer_phone`,
ADD COLUMN `shipping_city` VARCHAR(100) NOT NULL AFTER `shipping_province`,
ADD COLUMN `shipping_address_line` TEXT NOT NULL AFTER `shipping_city`,
ADD COLUMN `shipping_postal_code` VARCHAR(20) NOT NULL AFTER `shipping_address_line`;
-- Rename old columns to avoid confusion, but keep them for any old data
ALTER TABLE `orders`
CHANGE `customer_name` `billing_name` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
CHANGE `customer_email` `billing_email` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
CHANGE `customer_address` `legacy_customer_address` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;

View File

@ -0,0 +1,9 @@
CREATE TABLE IF NOT EXISTS `otp_codes` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`email` VARCHAR(255) NOT NULL,
`code_hash` VARCHAR(255) NOT NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`expires_at` TIMESTAMP NOT NULL,
`is_used` BOOLEAN NOT NULL DEFAULT FALSE,
INDEX `email_index` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@ -37,9 +37,9 @@
<h5 class="fw-bold mb-3">ما را دنبال کنید</h5>
<p class="text-white-50">از جدیدترین محصولات و تخفیف‌ها باخبر شوید.</p>
<div class="d-flex mt-3 social-icons">
<a href="#" class="btn btn-outline-primary me-2"><i class="bi bi-instagram"></i></a>
<a href="#" class="btn btn-outline-primary me-2"><i class="bi bi-telegram"></i></a>
<a href="#" class="btn btn-outline-primary"><i class="bi bi-whatsapp"></i></a>
<a href="#" class="social-icon me-3"><i class="bi bi-instagram"></i></a>
<a href="#" class="social-icon me-3"><i class="bi bi-telegram"></i></a>
<a href="#" class="social-icon"><i class="bi bi-whatsapp"></i></a>
</div>
</div>
</div>

View File

@ -20,8 +20,9 @@ $page_title = $page_title ?? 'فروشگاه آتیمه'; // Default title
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.rtl.min.css" rel="stylesheet">
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
<!-- Remix Icon CSS -->
<link href="https://cdn.jsdelivr.net/npm/remixicon@4.2.0/fonts/remixicon.css" rel="stylesheet" />
<!-- AOS CSS -->
<link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
@ -67,7 +68,7 @@ $page_title = $page_title ?? 'فروشگاه آتیمه'; // Default title
<a class="nav-link" href="shop.php">فروشگاه</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">درباره ما</a>
<a class="nav-link" href="about.php">درباره ما</a>
</li>
<li class="nav-item">
<a class="nav-link" href="contact.php">تماس با ما</a>
@ -75,11 +76,8 @@ $page_title = $page_title ?? 'فروشگاه آتیمه'; // Default title
</ul>
<div class="d-flex align-items-center">
<button id="theme-toggle" class="btn me-3">
<i class="bi bi-moon-stars-fill"></i>
</button>
<a href="cart.php" class="ms-4 position-relative">
<i class="bi bi-bag fs-5"></i>
<i class="ri-shopping-bag-line fs-5"></i>
<?php if ($cart_item_count > 0): ?>
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
<?php echo $cart_item_count; ?>
@ -87,9 +85,25 @@ $page_title = $page_title ?? 'فروشگاه آتیمه'; // Default title
</span>
<?php endif; ?>
</a>
<a href="/admin/login.php" class="ms-3">
<i class="bi bi-person fs-5"></i>
</a>
<?php if (isset($_SESSION['user_id'])): ?>
<div class="dropdown ms-3">
<a href="#" class="d-flex align-items-center text-decoration-none dropdown-toggle" id="dropdownUser" data-bs-toggle="dropdown" aria-expanded="false">
<i class="ri-user-line fs-5 me-1"></i>
<span><?php echo htmlspecialchars($_SESSION['user_name']); ?></span>
</a>
<ul class="dropdown-menu dropdown-menu-end text-small shadow" aria-labelledby="dropdownUser">
<li><a class="dropdown-item" href="profile.php">حساب کاربری</a></li>
<?php if (!empty($_SESSION['is_admin'])): ?>
<li><a class="dropdown-item" href="/admin/index.php">پنل مدیریت</a></li>
<?php endif; ?>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="logout.php">خروج</a></li>
</ul>
</div>
<?php else: ?>
<a href="login.php" class="btn btn-primary btn-sm ms-3">ورود / ثبت‌نام</a>
<?php endif; ?>
</div>
</div>
</div>

View File

@ -22,9 +22,19 @@ include 'includes/header.php';
<!-- Featured Products Section -->
<section id="featured-products" class="py-5">
<div class="container">
<?php
if (isset($_SESSION['success_message'])) {
echo '<div class="alert alert-success alert-dismissible fade show" role="alert">' . $_SESSION['success_message'] . '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>';
unset($_SESSION['success_message']);
}
if (isset($_SESSION['error_message'])) {
echo '<div class="alert alert-danger alert-dismissible fade show" role="alert">' . $_SESSION['error_message'] . '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>';
unset($_SESSION['error_message']);
}
?>
<div class="text-center mb-5" data-aos="fade-up">
<h2 class="display-5 fw-bold">مجموعه برگزیده ما</h2>
<p class="text-muted fs-5">دست‌چین شده برای سلیقه‌های خاص.</p>
<p class="fs-5">دست‌چین شده برای سلیقه‌های خاص.</p>
</div>
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4 g-lg-5">
<?php

84
login.php Normal file
View File

@ -0,0 +1,84 @@
<?php
session_start();
if (isset($_SESSION['user_id'])) {
header('Location: index.php');
exit;
}
$page_title = "ورود یا ثبت‌نام";
// We don't include the standard header/footer as this is a standalone page design
?>
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo $page_title; ?> - آتیمه</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.rtl.min.css" rel="stylesheet">
<!-- Vazirmatn Font -->
<link href="https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@v33.003/Vazirmatn-font-face.css" rel="stylesheet" type="text/css" />
<!-- Main Custom CSS (for variables) -->
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<!-- Custom Auth CSS -->
<link rel="stylesheet" href="assets/css/auth_style.css?v=<?php echo time(); ?>">
</head>
<body>
<div class="auth-wrapper">
<!-- Left Side: Background Image and Branding -->
<div class="auth-bg">
<div class="auth-bg-content">
<h1>به خانه چرم بازگردید</h1>
<p>اصالت و زیبایی در دستان شما. برای ورود یا ساخت حساب کاربری، ایمیل خود را وارد کنید.</p>
</div>
</div>
<!-- Right Side: Form -->
<div class="auth-form-wrapper">
<div class="auth-form-container">
<div class="form-header text-center">
<h2>ورود یا ثبت‌نام</h2>
<p>برای دریافت کد یکبار مصرف، ایمیل خود را وارد کنید.</p>
</div>
<?php if(isset($_SESSION['flash_message'])): ?>
<div class="alert alert-<?php echo $_SESSION['flash_message']['type']; ?> alert-dismissible fade show" role="alert">
<?php echo htmlspecialchars($_SESSION['flash_message']['message']); ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php unset($_SESSION['flash_message']); ?>
<?php endif; ?>
<form action="auth_handler.php?action=send_otp" method="POST">
<div class="form-group">
<label for="email" class="form-label visually-hidden">ایمیل</label>
<input type="email" class="form-control" id="email" name="email" placeholder="ایمیل خود را وارد کنید" required>
</div>
<div class="d-grid mt-4">
<button type="submit" class="btn btn-primary">ادامه با ایمیل</button>
</div>
</form>
<div class="separator my-4"><span>یا</span></div>
<div class="d-grid">
<button class="btn btn-google" disabled>
<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>
</div>
<div class="auth-footer">
<p><a href="index.php">بازگشت به صفحه اصلی</a></p>
</div>
</div>
</div>
</div>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

3
logout.php Normal file
View File

@ -0,0 +1,3 @@
<?php
// This file provides a simple entry point for logging out.
require_once __DIR__ . '/auth_handler.php';

View File

@ -1,10 +1,13 @@
<?php
session_start();
require_once 'db/config.php';
require_once 'includes/header.php';
$product_id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
$product_id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
$product = null;
$db_error = '';
if ($product_id <= 0) {
if (!$product_id) {
// Redirect or show error if ID is not valid
header("Location: shop.php");
exit;
}
@ -14,62 +17,76 @@ try {
$stmt = $pdo->prepare("SELECT * FROM products WHERE id = ?");
$stmt->execute([$product_id]);
$product = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$product) {
header("Location: shop.php");
exit;
}
} catch (PDOException $e) {
error_log("DB Error: " . $e->getMessage());
die("An error occurred. Please try again later.");
error_log("Database Error: " . $e->getMessage());
$db_error = "<p>خطا در برقراری ارتباط با پایگاه داده.</p>";
}
$page_title = htmlspecialchars($product['name']);
$available_colors = !empty($product['colors']) ? array_map('trim', explode(',', $product['colors'])) : [];
// If product not found, show a message and stop
if (!$product) {
echo '<main class="container py-5 text-center"><div class="alert alert-danger">محصولی با این شناسه یافت نشد.</div><div><a href="shop.php" class="btn btn-primary mt-3">بازگشت به فروشگاه</a></div></main>';
require_once 'includes/footer.php';
exit;
}
// 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
}
include 'includes/header.php';
?>
<div class="row g-5">
<div class="col-lg-6" data-aos="zoom-in">
<img src="<?php echo htmlspecialchars($product['image_url']); ?>" class="img-fluid rounded-4 shadow-lg w-100" alt="<?php echo htmlspecialchars($product['name']); ?>" style="aspect-ratio: 1/1; object-fit: cover;">
</div>
<div class="col-lg-6 d-flex flex-column justify-content-center">
<div data-aos="fade-right" data-aos-delay="100">
<h1 class="display-4 fw-bold"><?php echo htmlspecialchars($product['name']); ?></h1>
<p class="lead text-white-50 my-3"><?php echo htmlspecialchars($product['description']); ?></p>
</div>
<div data-aos="fade-up" data-aos-delay="200">
<div class="display-5 fw-bold my-4 text-gold"><?php echo number_format($product['price']); ?> <span class="fs-5 text-white-50">تومان</span></div>
</div>
<form action="cart_handler.php" method="POST" data-aos="fade-up" data-aos-delay="300">
<input type="hidden" name="product_id" value="<?php echo $product['id']; ?>">
<?php if (!empty($available_colors)): ?>
<div class="mb-4">
<label class="form-label fw-bold fs-5 mb-3">انتخاب رنگ:</label>
<div class="d-flex flex-wrap gap-3 color-swatches">
<?php foreach ($available_colors as $index => $color): ?>
<div data-bs-toggle="tooltip" title="<?php echo htmlspecialchars($color); ?>">
<input type="radio" class="btn-check" name="color" id="color-<?php echo $index; ?>" value="<?php echo htmlspecialchars($color); ?>" autocomplete="off" <?php echo $index === 0 ? 'checked' : ''; ?>>
<label class="btn" for="color-<?php echo $index; ?>"><?php echo htmlspecialchars($color); ?></label>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<div class="d-flex align-items-center mb-4">
<label for="quantity" class="form-label ms-3 mb-0 fs-5">تعداد:</label>
<input type="number" name="quantity" id="quantity" class="form-control bg-dark text-white" value="1" min="1" max="10" style="width: 80px;">
</div>
<button type="submit" name="add_to_cart" class="btn btn-primary btn-lg w-100 py-3 fw-bold">افزودن به سبد خرید</button>
</form>
<main class="container py-5">
<div class="row g-5">
<div class="col-lg-6" data-aos="fade-right">
<div class="product-image-gallery">
<img src="<?php echo htmlspecialchars($product['image_url']); ?>" class="img-fluid rounded-4 shadow-lg" alt="<?php echo htmlspecialchars($product['name']); ?>">
</div>
</div>
<?php include 'includes/footer.php'; ?>
<div class="col-lg-6" data-aos="fade-left">
<h1 class="display-5 fw-bold mb-3"><?php echo htmlspecialchars($product['name']); ?></h1>
<div class="d-flex align-items-center mb-4">
<p class="display-6 text-primary fw-bold m-0"><?php echo number_format($product['price']); ?> تومان</p>
</div>
<p class="fs-5 mb-4"><?php echo nl2br(htmlspecialchars($product['description'])); ?></p>
<form action="cart_handler.php" method="POST">
<input type="hidden" name="product_id" value="<?php echo $product['id']; ?>">
<input type="hidden" name="action" value="add">
<?php if (!empty($available_colors)): ?>
<div class="mb-4">
<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' : ''; ?>>
<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>
</div>
<?php endif; ?>
<div class="row align-items-center mb-4">
<div class="col-md-5 col-lg-4">
<label for="quantity" class="form-label fw-bold">تعداد:</label>
<input type="number" name="quantity" id="quantity" class="form-control form-control-lg bg-dark text-center" value="1" min="1" max="10">
</div>
</div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary btn-lg"><i class="fas fa-shopping-cart me-2"></i> افزودن به سبد خرید</button>
</div>
</form>
</div>
</div>
</main>
<?php require_once 'includes/footer.php'; ?>

119
profile.php Normal file
View File

@ -0,0 +1,119 @@
<?php
session_start();
// 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'];
// Fetch user data
try {
$pdo = db();
$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);
} catch (PDOException $e) {
die("Error fetching user data: " . $e->getMessage());
}
$page_title = 'حساب کاربری من';
require_once 'includes/header.php';
?>
<style>
.profile-nav .nav-link {
color: #6c757d;
border-bottom: 2px solid transparent;
}
.profile-nav .nav-link.active {
color: var(--bs-primary);
border-bottom-color: var(--bs-primary);
}
</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>
</div>
</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>
<?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>
<?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>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
<?php
require_once 'includes/footer.php';
?>

View File

@ -1,51 +1,57 @@
<?php
$page_title = 'فروشگاه';
require_once 'includes/header.php';
require_once 'db/config.php';
// Fetch all products from the database
try {
$pdo = db();
$stmt = $pdo->query("SELECT * FROM products ORDER BY created_at DESC");
$products = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
error_log("DB Error: " . $e->getMessage());
error_log("Database error: " . $e->getMessage());
$products = [];
$db_error = "خطا در بارگذاری محصولات. لطفا بعدا تلاش کنید.";
}
$page_title = 'فروشگاه';
include 'includes/header.php';
?>
<div class="text-center mb-5">
<h1 class="display-4 fw-bold">گالری محصولات</h1>
<p class="lead text-muted">دست‌سازه‌هایی از چرم طبیعی، با عشق و دقت</p>
</div>
<main class="container py-5">
<div class="text-center mb-5" data-aos="fade-down">
<h1 class="display-4 fw-bold">مجموعه کامل محصولات</h1>
<p class="fs-5">دست‌سازه‌هایی از چرم طبیعی، با عشق و دقت.</p>
</div>
<div class="row row-cols-1 row-cols-sm-2 row-cols-lg-3 row-cols-xl-4 g-4">
<?php if (!empty($products)):
$delay = 0;
foreach ($products as $product):
?>
<div class="col" data-aos="fade-up" data-aos-delay="<?php echo $delay; ?>">
<div class="product-card h-100">
<div class="product-image">
<a href="product.php?id=<?php echo $product['id']; ?>">
<img src="<?php echo htmlspecialchars($product['image_url']); ?>" class="img-fluid" alt="<?php echo htmlspecialchars($product['name']); ?>">
</a>
</div>
<div class="product-info text-center">
<h3 class="product-title"><a href="product.php?id=<?php echo $product['id']; ?>" class="text-decoration-none"><?php echo htmlspecialchars($product['name']); ?></a></h3>
<p class="product-price"><?php echo number_format($product['price']); ?> تومان</p>
</div>
</div>
</div>
<?php
$delay += 100;
endforeach;
?>
<?php else: ?>
<div class="col-12">
<p class="text-center p-5 bg-light rounded-3">محصولی برای نمایش یافت نشد.</p>
<?php if (!empty($db_error)): ?>
<div class="alert alert-danger">
<?php echo $db_error; ?>
</div>
<?php endif; ?>
</div>
<?php include 'includes/footer.php'; ?>
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-3 row-cols-lg-4 g-4 g-lg-5">
<?php
if (!empty($products)) {
$delay = 0;
foreach ($products as $product) {
echo '<div class="col" data-aos="fade-up" data-aos-delay="' . $delay . '">';
echo ' <div class="product-card h-100">';
echo ' <div class="product-image">';
echo ' <a href="product.php?id=' . htmlspecialchars($product['id']) . '">';
echo ' <img src="' . htmlspecialchars($product['image_url']) . '" class="img-fluid" alt="' . htmlspecialchars($product['name']) . '">';
echo ' </a>';
echo ' </div>';
echo ' <div class="product-info text-center">';
echo ' <h3 class="product-title"><a href="product.php?id=' . htmlspecialchars($product['id']) . '" class="text-decoration-none">' . htmlspecialchars($product['name']) . '</a></h3>';
echo ' <p class="product-price">' . number_format($product['price']) . ' تومان</p>';
echo ' </div>';
echo ' </div>';
echo '</div>';
$delay = ($delay + 100) % 400; // Stagger animation delay
}
} else if (empty($db_error)) {
echo '<div class="col-12"><p class="text-center text-white-50 fs-4">در حال حاضر محصولی برای نمایش وجود ندارد.</p></div>';
}
?>
</div>
</main>
<?php require_once 'includes/footer.php'; ?>

75
verify.php Normal file
View File

@ -0,0 +1,75 @@
<?php
session_start();
// Redirect if email is not in session (user hasn't come from login page)
if (!isset($_SESSION['otp_email'])) {
header('Location: login.php');
exit;
}
$email_for_display = htmlspecialchars($_SESSION['otp_email']);
$page_title = "تایید کد یکبار مصرف";
?>
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo $page_title; ?> - آتیمه</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.rtl.min.css" rel="stylesheet">
<!-- Vazirmatn Font -->
<link href="https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@v33.003/Vazirmatn-font-face.css" rel="stylesheet" type="text/css" />
<!-- Main Custom CSS (for variables) -->
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<!-- Custom Auth CSS -->
<link rel="stylesheet" href="assets/css/auth_style.css?v=<?php echo time(); ?>">
</head>
<body>
<div class="auth-wrapper">
<div class="auth-bg">
<div class="auth-bg-content">
<h1>فقط یک قدم دیگر...</h1>
<p>کد تاییدی که به ایمیل شما ارسال شده را وارد کنید تا وارد دنیای شگفت‌انگیز چرم شوید.</p>
</div>
</div>
<div class="auth-form-wrapper">
<div class="auth-form-container">
<div class="form-header text-center">
<h2>تایید کد</h2>
<p>کد ۶ رقمی ارسال شده به <strong><?php echo $email_for_display; ?></strong> را وارد کنید.</p>
</div>
<?php if(isset($_SESSION['flash_message'])): ?>
<div class="alert alert-<?php echo $_SESSION['flash_message']['type']; ?> alert-dismissible fade show" role="alert">
<?php echo htmlspecialchars($_SESSION['flash_message']['message']); ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php unset($_SESSION['flash_message']); ?>
<?php endif; ?>
<form action="auth_handler.php?action=verify_otp" method="POST">
<input type="hidden" name="email" value="<?php echo $email_for_display; ?>">
<div class="form-group">
<label for="otp_code" class="form-label visually-hidden">کد تایید</label>
<input type="text" class="form-control text-center" id="otp_code" name="otp_code" placeholder="- - - - - -" required pattern="\d{6}" maxlength="6">
</div>
<div class="d-grid mt-4">
<button type="submit" class="btn btn-primary">تایید و ورود</button>
</div>
</form>
<div class="auth-footer">
<p>ایمیل را اشتباه وارد کردید؟ <a href="login.php">بازگشت</a></p>
</div>
</div>
</div>
</div>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>