diff --git a/about.php b/about.php new file mode 100644 index 00000000..aff5f294 --- /dev/null +++ b/about.php @@ -0,0 +1,61 @@ + + +
+
+

داستان آتیمه

+

تلفیق هنر سنتی و طراحی مدرن

+
+ +
+
+
+
+
+ هنر چرم‌دوزی +
+
+
+

باور ما

+

ما در آتیمه، به قدرت دست‌ها و اصالت مواد اولیه باور داریم. داستان ما از یک کارگاه کوچک و عشقی عمیق به هنر چرم‌دوزی آغاز شد. هدف ما خلق آثاری است که نه تنها یک وسیله کاربردی، بلکه بخشی از داستان و استایل روزمره شما باشند؛ آثاری که با گذر زمان، زیباتر و شخصی‌تر می‌شوند.

+

هر محصول، حاصل ساعت‌ها کار دست هنرمندان ماهر و استفاده از بهترین و باکیفیت‌ترین چرم‌های طبیعی است. ما به جزئیات اهمیت می‌دهیم، از انتخاب نخ گرفته تا طراحی هر برش و دوخت. این تعهد به کیفیت، تضمین می‌کند که هر ساخته‌ دست ما، اثری ماندگار و بی‌همتا باشد.

+ مشاهده مجموعه ما +
+
+
+
+
+
+ + +
+
+
+
+ +

تعهد به کیفیت

+

استفاده از بهترین مواد اولیه و کنترل کیفی دقیق در تمام مراحل تولید.

+
+
+
+
+ +

هنر دست

+

تمام محصولات ما با عشق و دقت توسط هنرمندان ماهر ساخته می‌شوند.

+
+
+
+
+ +

طراحی ماندگار

+

خلق آثاری مدرن و در عین حال کلاسیک که هیچ‌گاه از مد نمی‌افتند.

+
+
+
+
+ +
+ + diff --git a/admin/add_product.php b/admin/add_product.php index cd330eef..24c01468 100644 --- a/admin/add_product.php +++ b/admin/add_product.php @@ -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']); } ?> - - - - - - افزودن محصول جدید - - - - - - - -
-
-
-
-

افزودن محصول جدید

- بازگشت -
-
-
-
-
- - -
-
- - -
-
+
+
+
+

افزودن محصول جدید

+ انصراف +
+ +
+
+ +
+ + +
+
+ + +
+
+
- +
-
+
- +
-
- - -
کدهای رنگ هگزادسیمال را با کاما جدا کنید.
-
-
- - -
- - -
+
+
+ + +
کدهای رنگ هگزادسیمال را با کاما جدا کنید.
+
+
+ + +
+
+ +
+
- + - - \ No newline at end of file + + diff --git a/admin/assets/css/admin_style.css b/admin/assets/css/admin_style.css new file mode 100644 index 00000000..225def67 --- /dev/null +++ b/admin/assets/css/admin_style.css @@ -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; + } +} diff --git a/admin/auth_check.php b/admin/auth_check.php index 00a8177e..6beff4c4 100644 --- a/admin/auth_check.php +++ b/admin/auth_check.php @@ -1,5 +1,7 @@ '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'; ?> - - - - - - ویرایش محصول: <?php echo htmlspecialchars($product['name']); ?> - - - - - - - -
-
-
-

ویرایش محصول

-
-
-
- - + -
- - +
+
+
فرم ویرایش محصول
+ + بازگشت + +
+
+ + + + +
+ +
+
+ + +
+ +
+ + +
+ +
+
+
+ + +
-
- - +
+
+ + +
رنگ‌ها را با کاما جدا کنید (مثال: #FFFFFF, #000000).
+
-
- - +
+
+ > + +
+
+ + +
+
+ +
+ Current Image + + برای تغییر، تصویر جدید را انتخاب کنید.
-
- - -
رنگ‌های موجود را با کاما از هم جدا کنید (مثال: #FFFFFF, #000000).
-
-
- - -
تصویر فعلی:
- Current Image -
-
- > - -
-
- انصراف - -
- +
-
+ +
+ انصراف + +
+
- + - - diff --git a/admin/footer.php b/admin/footer.php new file mode 100644 index 00000000..ae45f072 --- /dev/null +++ b/admin/footer.php @@ -0,0 +1,7 @@ +
+ + + + + + diff --git a/admin/handler.php b/admin/handler.php index 4e45b292..4633b63d 100644 --- a/admin/handler.php +++ b/admin/handler.php @@ -1,10 +1,9 @@ + "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 = 'لطفاً تمام خطاها را برطرف کنید:

' . implode('
', $errors); - $_SESSION['flash_message'] = ['type' => 'error', 'message' => $error_message]; + $_SESSION['flash_message'] = ['type' => 'error', 'message' => implode("
", $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 = 'فرم دارای خطا است:

' . implode('
', $errors); - $_SESSION['flash_message'] = ['type' => 'error', 'message' => $error_message]; + $_SESSION['flash_message'] = ['type' => 'error', 'message' => implode("
", $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; diff --git a/admin/header.php b/admin/header.php new file mode 100644 index 00000000..c36199f7 --- /dev/null +++ b/admin/header.php @@ -0,0 +1,27 @@ + + + + + + پنل مدیریت + + + + + + + + + + + + + + + + + + + +
+
diff --git a/admin/index.php b/admin/index.php index 8b18fbcb..4c107224 100644 --- a/admin/index.php +++ b/admin/index.php @@ -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 = "خطا در بارگذاری اطلاعات داشبورد: " . $e->getMessage(); + $dashboard_error .= "

این خطا معمولاً به دلیل قدیمی بودن ساختار دیتابیس رخ می‌دهد. لطفاً برای به‌روزرسانی به صفحه مایگریشن بروید."; } $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'; + } +} ?> - - - - - - پنل مدیریت - محصولات - - - - - - - -
-
-

مدیریت محصولات

-
- + افزودن محصول جدید - خروج +

داشبورد

+ + +
+ + + +
+
+
+
+ +
+
+

کل محصولات

+

+
+
+
+
+
+
+ +
+
+

کل سفارشات

+

+
+
-
- - - - - - - - - - - - - - - - + +
+
+
آخرین سفارشات
+
+
+
+
#نام محصولقیمتعملیات
هیچ محصولی یافت نشد.
+ - - - - + + + + + + - - - -
تومان - ویرایش - حذف - شماره سفارشنام مشتریمبلغ کلوضعیتتاریخ
+ + + + + هیچ سفارشی یافت نشد. + + + + + # + + تومان + + + + مشاهده + + + + + + +
+
- -
+ - + - - \ No newline at end of file + + diff --git a/admin/login.php b/admin/login.php index 1853da4f..1b52b12f 100644 --- a/admin/login.php +++ b/admin/login.php @@ -32,6 +32,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { + @@ -43,7 +44,21 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {

ورود به پنل

رمز عبور: admin123

-
+
diff --git a/admin/nav.php b/admin/nav.php new file mode 100644 index 00000000..13d9f296 --- /dev/null +++ b/admin/nav.php @@ -0,0 +1,42 @@ + + + + \ No newline at end of file diff --git a/admin/orders.php b/admin/orders.php new file mode 100644 index 00000000..9d01990c --- /dev/null +++ b/admin/orders.php @@ -0,0 +1,131 @@ +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'; + } +} +?> + +
+

مدیریت سفارشات

+
+ + +
+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
شمارهنام مشتریمبلغ کلوضعیتتاریخعملیات
هیچ سفارشی یافت نشد.
# تومان + +
+
+
+
+ + diff --git a/admin/products.php b/admin/products.php new file mode 100644 index 00000000..e25e2547 --- /dev/null +++ b/admin/products.php @@ -0,0 +1,119 @@ +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']); +} + +?> + + +
+

مدیریت محصولات

+ + افزودن محصول + +
+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
#نام محصولقیمتعملیات
هیچ محصولی یافت نشد.
تومان + + ویرایش + + + حذف + +
+
+
+
+ + + + + diff --git a/admin/test.php b/admin/test.php new file mode 100644 index 00000000..c9f5eeb1 --- /dev/null +++ b/admin/test.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/css/auth_style.css b/assets/css/auth_style.css new file mode 100644 index 00000000..c125ea35 --- /dev/null +++ b/assets/css/auth_style.css @@ -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; + +} diff --git a/assets/css/custom.css b/assets/css/custom.css index b650755b..0eee4c19 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -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 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; +} diff --git a/assets/images/products/product_6930ac914bc555.82600063_PHP 8.2.29 - phpinfo() - Google Chrome 12_09_1404 10_38_28 ب.ظ.png b/assets/images/products/product_6930ac914bc555.82600063_PHP 8.2.29 - phpinfo() - Google Chrome 12_09_1404 10_38_28 ب.ظ.png new file mode 100644 index 00000000..820b70f0 Binary files /dev/null and b/assets/images/products/product_6930ac914bc555.82600063_PHP 8.2.29 - phpinfo() - Google Chrome 12_09_1404 10_38_28 ب.ظ.png differ diff --git a/assets/js/main.js b/assets/js/main.js index e9f42b87..fa0b40e5 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -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 = ''; - } else { - htmlElement.classList.remove('dark'); - if (themeToggleButton) themeToggleButton.innerHTML = ''; - } - } - - 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, }); - }); diff --git a/assets/pasted-20251203-190906-4bf15fd7.png b/assets/pasted-20251203-190906-4bf15fd7.png new file mode 100644 index 00000000..820b70f0 Binary files /dev/null and b/assets/pasted-20251203-190906-4bf15fd7.png differ diff --git a/auth_handler.php b/auth_handler.php new file mode 100644 index 00000000..001c89c6 --- /dev/null +++ b/auth_handler.php @@ -0,0 +1,147 @@ +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 = "

کد تایید شما

برای ورود یا ثبت‌نام در وب‌سایت آتیمه، از کد زیر استفاده کنید:

{$otp_code}

این کد تا ۱۰ دقیقه دیگر معتبر است.

"; + + $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; +} \ No newline at end of file diff --git a/cart.php b/cart.php index 035ae582..3ce4acbc 100644 --- a/cart.php +++ b/cart.php @@ -1,121 +1,104 @@ '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; ?> -
-

سبد خرید شما

+
+
+ + +
+ +

سبد خرید شما خالی است

+

به نظر می‌رسد هنوز محصولی به سبد خرید خود اضافه نکرده‌اید. همین حالا گشتی در فروشگاه بزنید.

+ + + رفتن به فروشگاه + +
+ +
+

سبد خرید شما

+

جزئیات سفارش خود را بررسی و نهایی کنید.

+
+
+
+ $item): + $item_total = $item['price'] * $item['quantity']; + $total_price += $item_total; + ?> +
+
+ + + + + + +
+
+
+ + <?php echo htmlspecialchars($item['name']); ?> + +
+
+
+ +
+ رنگ: + +
+ +
+
+
+ + + + + + + +
+
+
+ تومان +
+
+
+ +
+ +
+
+

خلاصه سفارش

+
+ جمع کل + تومان +
+
+ هزینه ارسال + رایگان +
+
+
+ مبلغ نهایی + تومان +
+
+ +
+
+
+ +
- -
-

سبد خرید شما خالی است.

- بازگشت به فروشگاه -
- -
- -
- - - - - - - - - - - - - - - - - - - - - - -
محصولقیمتتعدادجمع کلحذف
- <?php echo htmlspecialchars($item['name']); ?> - -
- - رنگ: - -
- - - × -
-
- -
- -
-

جمع نهایی: تومان

-
-
-
- - - - - + \ No newline at end of file diff --git a/cart_handler.php b/cart_handler.php index 6f07fcbd..f62a8d07 100644 --- a/cart_handler.php +++ b/cart_handler.php @@ -1,70 +1,77 @@ 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; \ No newline at end of file +// Redirect back to the cart page to show changes +header('Location: cart.php'); +exit; diff --git a/checkout.php b/checkout.php index b9f0ab8a..01011d85 100644 --- a/checkout.php +++ b/checkout.php @@ -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'; ?> - - - - - - <?php echo $p_title; ?> - چرم آتیمه - - - - - - - -
-
-
- -

آتیمه

-
- -
- - سبد خرید - - - - +
+ ' . htmlspecialchars($_SESSION['error_message']) . '
'; + unset($_SESSION['error_message']); + } + ?> +
+

تکمیل فرآیند خرید

+

فقط یک قدم دیگر تا نهایی شدن سفارش شما باقیست.

+
+ +
+
+ +
+

اطلاعات ارسال

+ + +
+ + +
+ + +
+
+
+
+ + +
+
+ + +
+
+ + + +
توجه: فقط با شماره تلفن همراه میتوان سفارش را رهگیری کرد.
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+
+
+ + +
+
+
+

خلاصه سفارش

+
    + +
  • +
    + <?php echo htmlspecialchars($item['name']); ?> +
    + + تعداد: +
    +
    + +
  • + +
+ +
+ جمع کل + تومان +
+
+ هزینه ارسال + رایگان +
+
+
+ مبلغ نهایی + تومان +
+
+ +
+
+ پرداخت امن و مطمئن +
+
-
+ +
- -
-
-

-
+ -
-
- -
-

از خرید شما متشکریم!

-

سفارش شما با موفقیت ثبت شد و به زودی پردازش خواهد شد. یک ایمیل تایید برای شما ارسال گردید.

- بازگشت به فروشگاه -
- - -
.
- -
-
-
اطلاعات ارسال
-
-
- - -
-
- - -
-
- - -
-
- -
-
-
-
- -
-
-
- - -
-
-

© چرم آتیمه. تمام حقوق محفوظ است.

-
-
- - - - \ No newline at end of file + \ No newline at end of file diff --git a/checkout_handler.php b/checkout_handler.php new file mode 100644 index 00000000..34b07dc2 --- /dev/null +++ b/checkout_handler.php @@ -0,0 +1,100 @@ +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; +} diff --git a/contact.php b/contact.php index 53e84473..6bed3002 100644 --- a/contact.php +++ b/contact.php @@ -1,73 +1,69 @@ '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']); +} ?> +
-
-

-

-
-
- -
-
-
- - - - - - - +
+

ارتباط با ما

+

نظرات، پیشنهادات و سوالات شما برای ما ارزشمند است.

+
-
+
+
+ +
- +
- - -
-
- - + +
- +
@@ -75,8 +71,24 @@ include 'includes/header.php';
-
توجه: این فرم برای اهداف آزمایشی است. برای دریافت واقعی ایمیل‌ها، باید اطلاعات سرور ایمیل (SMTP) خود را در فایل .env وارد کنید.
+
- \ No newline at end of file + + + + diff --git a/db/migrations/001_add_colors_to_products.sql b/db/migrations/001_add_colors_to_products.sql index 643dd22c..d41b0b1b 100644 --- a/db/migrations/001_add_colors_to_products.sql +++ b/db/migrations/001_add_colors_to_products.sql @@ -1 +1,2 @@ -ALTER TABLE products ADD COLUMN colors VARCHAR(255) DEFAULT NULL COMMENT 'Comma-separated list of available colors'; \ No newline at end of file +-- 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'; diff --git a/db/migrations/002_add_is_featured_to_products.sql b/db/migrations/002_add_is_featured_to_products.sql index e1f692b9..619f565f 100644 --- a/db/migrations/002_add_is_featured_to_products.sql +++ b/db/migrations/002_add_is_featured_to_products.sql @@ -1 +1,2 @@ -ALTER TABLE products ADD COLUMN is_featured BOOLEAN DEFAULT 0; \ No newline at end of file +-- 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; diff --git a/db/migrations/003_create_orders_table.sql b/db/migrations/003_create_orders_table.sql new file mode 100644 index 00000000..c0962637 --- /dev/null +++ b/db/migrations/003_create_orders_table.sql @@ -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'; \ No newline at end of file diff --git a/db/migrations/004_create_users_table.sql b/db/migrations/004_create_users_table.sql new file mode 100644 index 00000000..fcabbb63 --- /dev/null +++ b/db/migrations/004_create_users_table.sql @@ -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; diff --git a/db/migrations/005_create_user_addresses_table.sql b/db/migrations/005_create_user_addresses_table.sql new file mode 100644 index 00000000..bb86cae2 --- /dev/null +++ b/db/migrations/005_create_user_addresses_table.sql @@ -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; diff --git a/db/migrations/006_add_is_admin_to_users.sql b/db/migrations/006_add_is_admin_to_users.sql new file mode 100644 index 00000000..d5ac87a3 --- /dev/null +++ b/db/migrations/006_add_is_admin_to_users.sql @@ -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`; diff --git a/db/migrations/007_update_orders_table.sql b/db/migrations/007_update_orders_table.sql new file mode 100644 index 00000000..312bb96f --- /dev/null +++ b/db/migrations/007_update_orders_table.sql @@ -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; + diff --git a/db/migrations/008_create_otp_table.sql b/db/migrations/008_create_otp_table.sql new file mode 100644 index 00000000..535016ad --- /dev/null +++ b/db/migrations/008_create_otp_table.sql @@ -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; diff --git a/includes/footer.php b/includes/footer.php index 1941d4a2..7e5be656 100644 --- a/includes/footer.php +++ b/includes/footer.php @@ -37,9 +37,9 @@
ما را دنبال کنید

از جدیدترین محصولات و تخفیف‌ها باخبر شوید.

diff --git a/includes/header.php b/includes/header.php index e713bbbf..c7b7a183 100644 --- a/includes/header.php +++ b/includes/header.php @@ -20,8 +20,9 @@ $page_title = $page_title ?? 'فروشگاه آتیمه'; // Default title - - + + + @@ -67,7 +68,7 @@ $page_title = $page_title ?? 'فروشگاه آتیمه'; // Default title فروشگاه
diff --git a/index.php b/index.php index e4658220..be1547fd 100644 --- a/index.php +++ b/index.php @@ -22,9 +22,19 @@ include 'includes/header.php';