diff --git a/README.md b/README.md new file mode 100644 index 0000000..3b28e60 --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +# Car Sells in Afghanistan - Modern Web Application + +This project is a modern, feature-rich web application for a car dealership in Afghanistan. It provides a platform for users to browse, book, and review cars, along with a comprehensive admin panel for managing the entire platform. + +This project was built using PHP, MySQL, and Bootstrap, and features a clean, responsive, and modern design. + +## Features + +- **Modern & Responsive Design:** A beautiful and intuitive user interface built with Bootstrap and custom modern styling. +- **Car Listings:** Browse a filterable and searchable list of available cars. +- **Detailed Car View:** View detailed information and images for each car. +- **User Authentication:** Secure user registration and login system. +- **Car Booking System:** Registered users can book cars, which reserves the car until an admin approves the sale. +- **Review System:** Users can leave ratings and reviews on cars they are interested in. +- **Comprehensive Admin Dashboard:** + - **Analytics:** View stats on total users, cars, sales, and pending bookings. See charts for sales over time and booking distributions. + - **User Management:** View, search, and manage user accounts. + - **Car Management:** Add, edit, and delete car listings. + - **Booking Management:** Approve or cancel car bookings. + - **Review Management:** Approve or delete user-submitted reviews. +- **Afghanistan-Specific Details:** Car listings include relevant details for the Afghan market, such as province and city. + +## Getting Started + +To get the application up and running on your local system, follow these steps. + +### Prerequisites + +You will need a LAMP (Linux, Apache, MySQL, PHP) or equivalent stack. +- Apache +- PHP 8.0+ +- MySQL or MariaDB + +### 1. Set up the Database + +1. **Create a database** in your MySQL/MariaDB server. For example: + ```sql + CREATE DATABASE car_dealership; + ``` +2. **Configure the connection.** Open `db/config.php` and update the following with your database details: + - `DB_HOST` + - `DB_NAME` + - `DB_USER` + - `DB_PASS` + +### 2. Run Installation Scripts + +Open your web browser and navigate to the following URLs in order. This will set up the necessary tables and seed them with initial data. + +1. `http:///db/setup_users.php` +2. `http:///db/setup_cars.php` +3. `http:///db/migrate.php` + +### 3. Access the Application + +Once the setup is complete, you can access the application in your browser: + +- **Main Site:** `http:///` +- **Admin Panel:** `http:///admin/` + +### 4. Admin Login + +- **Username:** `admin` +- **Password:** `123` + +It is highly recommended to change the default admin password after your first login. \ No newline at end of file diff --git a/admin/bookings.php b/admin/bookings.php index c316d10..7698c8f 100644 --- a/admin/bookings.php +++ b/admin/bookings.php @@ -12,22 +12,34 @@ $pdo = db(); // Handle booking status change if ($_SERVER['REQUEST_METHOD'] === 'POST') { - $bookingId = $_POST['booking_id']; - $carId = $_POST['car_id']; - - if (isset($_POST['approve'])) { - $pdo->prepare("UPDATE bookings SET status = 'approved' WHERE id = ?")->execute([$bookingId]); - $pdo->prepare("UPDATE cars SET status = 'sold' WHERE id = ?")->execute([$carId]); - } elseif (isset($_POST['cancel'])) { - $pdo->prepare("UPDATE bookings SET status = 'canceled' WHERE id = ?")->execute([$bookingId]); - $pdo->prepare("UPDATE cars SET status = 'for_sale' WHERE id = ?")->execute([$carId]); + $bookingId = filter_input(INPUT_POST, 'booking_id', FILTER_VALIDATE_INT); + $carId = filter_input(INPUT_POST, 'car_id', FILTER_VALIDATE_INT); + + if ($bookingId && $carId) { + $pdo->beginTransaction(); + try { + if (isset($_POST['approve'])) { + // Set booking to approved and car to sold + $pdo->prepare("UPDATE bookings SET status = 'approved' WHERE id = ?")->execute([$bookingId]); + $pdo->prepare("UPDATE cars SET status = 'sold' WHERE id = ?")->execute([$carId]); + } elseif (isset($_POST['cancel'])) { + // Set booking to cancelled and car back to for sale (approved) + $pdo->prepare("UPDATE bookings SET status = 'cancelled' WHERE id = ?")->execute([$bookingId]); + $pdo->prepare("UPDATE cars SET status = 'approved' WHERE id = ?")->execute([$carId]); + } + $pdo->commit(); + } catch (Exception $e) { + $pdo->rollBack(); + error_log("Booking status update failed: " . $e->getMessage()); + } } header("Location: bookings.php"); exit(); } +// Fetch bookings with user and car details $bookings = $pdo->query(" - SELECT b.id, b.status, b.booking_date, u.username, c.make, c.model, c.id as car_id + SELECT b.id, b.status, b.booking_date, u.username, u.email, c.make, c.model, c.id as car_id FROM bookings b JOIN users u ON b.user_id = u.id JOIN cars c ON b.car_id = c.id @@ -46,47 +58,55 @@ $projectName = 'Manage Bookings'; - - -
-
- -
-
+ +
+ +
+
+

Manage Bookings

+
- - - +
UserCarDateStatusActions
+ + + + + + + + + + + - + - - + + @@ -96,8 +116,8 @@ $projectName = 'Manage Bookings'; - - + + diff --git a/admin/cars.php b/admin/cars.php index 534bc3a..44db3ae 100644 --- a/admin/cars.php +++ b/admin/cars.php @@ -12,28 +12,29 @@ $pdo = db(); // Handle Add/Edit/Delete if ($_SERVER['REQUEST_METHOD'] === 'POST') { - // Delete + $carId = filter_input(INPUT_POST, 'car_id', FILTER_VALIDATE_INT); + if (isset($_POST['delete_car'])) { - $carId = $_POST['car_id']; $stmt = $pdo->prepare("DELETE FROM cars WHERE id = ?"); $stmt->execute([$carId]); header("Location: cars.php"); exit(); } - // Add/Edit - $carId = $_POST['car_id'] ?? null; - $make = $_POST['make']; - $model = $_POST['model']; - $year = $_POST['year']; - $price = $_POST['price']; - $status = $_POST['status']; - $description = $_POST['description']; + // Sanitize and validate inputs + $make = trim(filter_input(INPUT_POST, 'make', FILTER_SANITIZE_SPECIAL_CHARS)); + $model = trim(filter_input(INPUT_POST, 'model', FILTER_SANITIZE_SPECIAL_CHARS)); + $year = filter_input(INPUT_POST, 'year', FILTER_VALIDATE_INT); + $price = filter_input(INPUT_POST, 'price', FILTER_VALIDATE_FLOAT); + $status = in_array($_POST['status'], ['approved', 'pending', 'sold', 'reserved']) ? $_POST['status'] : 'pending'; + $description = trim(filter_input(INPUT_POST, 'description', FILTER_SANITIZE_SPECIAL_CHARS)); + $color = trim(filter_input(INPUT_POST, 'color', FILTER_SANITIZE_SPECIAL_CHARS)); + $mileage = filter_input(INPUT_POST, 'mileage', FILTER_VALIDATE_INT); $imageUrl = $_POST['existing_image']; // Keep existing image by default if (isset($_FILES['image']) && $_FILES['image']['error'] == 0) { $targetDir = "../assets/images/cars/"; - if (!is_dir($targetDir)) mkdir($targetDir, 0755, true); + if (!is_dir($targetDir)) mkdir($targetDir, 0775, true); $fileName = uniqid() . '-' . basename($_FILES["image"]["name"]); $targetFilePath = $targetDir . $fileName; if (move_uploaded_file($_FILES["image"]["tmp_name"], $targetFilePath)) { @@ -42,32 +43,37 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { } if ($carId) { // Update - $sql = "UPDATE cars SET make=?, model=?, year=?, price=?, status=?, description=?, image_url=? WHERE id=?"; + $sql = "UPDATE cars SET make=?, model=?, year=?, price=?, status=?, description=?, image_url=?, color=?, mileage=? WHERE id=?"; $stmt = $pdo->prepare($sql); - $stmt->execute([$make, $model, $year, $price, $status, $description, $imageUrl, $carId]); + $stmt->execute([$make, $model, $year, $price, $status, $description, $imageUrl, $color, $mileage, $carId]); } else { // Insert - $sql = "INSERT INTO cars (make, model, year, price, status, description, image_url) VALUES (?, ?, ?, ?, ?, ?, ?)"; + $sql = "INSERT INTO cars (make, model, year, price, status, description, image_url, color, mileage) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"; $stmt = $pdo->prepare($sql); - $stmt->execute([$make, $model, $year, $price, $status, $description, $imageUrl]); + $stmt->execute([$make, $model, $year, $price, $status, $description, $imageUrl, $color, $mileage]); } header("Location: cars.php"); exit(); } +// Fetching cars with filters $search = $_GET['search'] ?? ''; $filter_status = $_GET['status'] ?? 'all'; $sql = "SELECT * FROM cars"; $params = []; +$where = []; if (!empty($search)) { - $sql .= " WHERE (make LIKE ? OR model LIKE ?)"; + $where[] = "(make LIKE ? OR model LIKE ?)"; $params[] = "%$search%"; $params[] = "%$search%"; } if ($filter_status !== 'all') { - $sql .= (strpos($sql, 'WHERE') === false ? " WHERE" : " AND") . " status = ?"; + $where[] = "status = ?"; $params[] = $filter_status; } +if (!empty($where)) { + $sql .= " WHERE " . implode(' AND ', $where); +} $sql .= " ORDER BY created_at DESC"; $stmt = $pdo->prepare($sql); $stmt->execute($params); @@ -80,65 +86,39 @@ $projectName = 'Manage Cars'; - <?php echo htmlspecialchars($projectName); ?> + <?= htmlspecialchars($projectName) ?> - - + - - -
-
- -
-
+ +
+ +
+
+

Manage Cars

- +
-
-
-
-
- -
-
- -
-
CustomerCarBooking DateStatusActions
No bookings found.
+
+ +
+ + + + -
+ - - + +
+ + No actions
- - +
ImageMake/ModelYearPriceStatusActions
+ + - - - + + - + +
ImageMake & ModelPriceStatusYearActions
<?= htmlspecialchars($car['make']) ?><?= htmlspecialchars($car['make']) ?> $ - -
+ +
@@ -150,8 +130,8 @@ $projectName = 'Manage Cars'; - - + + @@ -166,25 +146,29 @@ $projectName = 'Manage Cars'; + diff --git a/admin/partials/sidebar.php b/admin/partials/sidebar.php new file mode 100644 index 0000000..09d5643 --- /dev/null +++ b/admin/partials/sidebar.php @@ -0,0 +1,37 @@ + + diff --git a/admin/reviews.php b/admin/reviews.php index 06361a8..8a1e99e 100644 --- a/admin/reviews.php +++ b/admin/reviews.php @@ -10,19 +10,24 @@ require_once '../db/config.php'; $pdo = db(); -// Handle review status change +// Handle review status change or deletion if ($_SERVER['REQUEST_METHOD'] === 'POST') { - $reviewId = $_POST['review_id']; - - if (isset($_POST['approve'])) { - $pdo->prepare("UPDATE reviews SET status = 'approved' WHERE id = ?")->execute([$reviewId]); - } elseif (isset($_POST['delete'])) { - $pdo->prepare("DELETE FROM reviews WHERE id = ?")->execute([$reviewId]); + $reviewId = filter_input(INPUT_POST, 'review_id', FILTER_VALIDATE_INT); + + if ($reviewId) { + if (isset($_POST['approve'])) { + $pdo->prepare("UPDATE reviews SET status = 'approved' WHERE id = ?")->execute([$reviewId]); + } elseif (isset($_POST['unapprove'])) { + $pdo->prepare("UPDATE reviews SET status = 'pending' WHERE id = ?")->execute([$reviewId]); + } elseif (isset($_POST['delete'])) { + $pdo->prepare("DELETE FROM reviews WHERE id = ?")->execute([$reviewId]); + } } header("Location: reviews.php"); exit(); } +// Fetch reviews with user and car details $reviews = $pdo->query(" SELECT r.id, r.rating, r.review, r.status, r.created_at, u.username, c.make, c.model FROM reviews r @@ -32,6 +37,15 @@ $reviews = $pdo->query(" ")->fetchAll(PDO::FETCH_ASSOC); $projectName = 'Manage Reviews'; + +function render_stars($rating) { + $rating = (int)$rating; + $html = ''; + for ($i = 1; $i <= 5; $i++) { + $html .= $i <= $rating ? '★' : '☆'; + } + return '' . $html . ''; +} ?> @@ -43,48 +57,48 @@ $projectName = 'Manage Reviews'; - - -
-
- -
-
+ +
+ +
+
+

Manage Reviews

+
- - - +
UserCarRatingReviewDateStatusActions
+ + + + + - + - - - - + + @@ -94,9 +108,9 @@ $projectName = 'Manage Reviews'; - - + + - + \ No newline at end of file diff --git a/admin/users.php b/admin/users.php index 70f00df..d861ee4 100644 --- a/admin/users.php +++ b/admin/users.php @@ -8,49 +8,49 @@ if (!isset($_SESSION['user_id']) || $_SESSION['role'] !== 'admin') { require_once '../db/config.php'; +$pdo = db(); + // Handle user actions (delete, toggle status) if ($_SERVER['REQUEST_METHOD'] === 'POST') { - $pdo = db(); - if (isset($_POST['delete_user'])) { - $userId = $_POST['user_id']; - $stmt = $pdo->prepare("DELETE FROM users WHERE id = ?"); - $stmt->execute([$userId]); - } elseif (isset($_POST['toggle_status'])) { - $userId = $_POST['user_id']; - $stmt = $pdo->prepare("UPDATE users SET status = CASE WHEN status = 'active' THEN 'disabled' ELSE 'active' END WHERE id = ?"); - $stmt->execute([$userId]); + $userId = filter_input(INPUT_POST, 'user_id', FILTER_VALIDATE_INT); + if ($userId && $userId != $_SESSION['user_id']) { // Prevent admin from deleting themselves + if (isset($_POST['delete_user'])) { + $stmt = $pdo->prepare("DELETE FROM users WHERE id = ?"); + $stmt->execute([$userId]); + } elseif (isset($_POST['toggle_status'])) { + $stmt = $pdo->prepare("UPDATE users SET status = IF(status = 'active', 'disabled', 'active') WHERE id = ?"); + $stmt->execute([$userId]); + } } header("Location: users.php"); exit(); } +// Fetch users with filters $search = $_GET['search'] ?? ''; -$filter = $_GET['filter'] ?? 'all'; +$filter_status = $_GET['status'] ?? 'all'; -try { - $pdo = db(); - $sql = "SELECT id, username, email, role, created_at, status FROM users"; - $params = []; +$sql = "SELECT id, username, email, role, created_at, status FROM users"; +$params = []; +$where = []; - if (!empty($search)) { - $sql .= " WHERE (username LIKE ? OR email LIKE ?)"; - $params[] = "%$search%"; - $params[] = "%$search%"; - } - - if ($filter !== 'all') { - $sql .= (strpos($sql, 'WHERE') === false ? " WHERE" : " AND") . " status = ?"; - $params[] = $filter; - } - - $sql .= " ORDER BY created_at DESC"; - $stmt = $pdo->prepare($sql); - $stmt->execute($params); - $users = $stmt->fetchAll(PDO::FETCH_ASSOC); -} catch (PDOException $e) { - error_log("Admin Users Page Error: " . $e->getMessage()); - $users = []; +if (!empty($search)) { + $where[] = "(username LIKE ? OR email LIKE ?)"; + $params[] = "%$search%"; + $params[] = "%$search%"; } +if ($filter_status !== 'all') { + $where[] = "status = ?"; + $params[] = $filter_status; +} +if (!empty($where)) { + $sql .= " WHERE " . implode(' AND ', $where); +} + +$sql .= " ORDER BY created_at DESC"; +$stmt = $pdo->prepare($sql); +$stmt->execute($params); +$users = $stmt->fetchAll(PDO::FETCH_ASSOC); $projectName = 'Manage Users'; ?> @@ -59,101 +59,70 @@ $projectName = 'Manage Users'; - <?php echo htmlspecialchars($projectName); ?> + <?= htmlspecialchars($projectName) ?> - - + - - - - -
-
- - -
-
+ +
+ +
+
+

Manage Users

-
-
-
- -
-
- -
-
- -
- -
-
ReviewerCarReviewStatusActions
No reviews found.
-
+
+ "" +
+ + + + + - + + + - +
- - - - - - - - - - +
IDUsernameEmailRoleStatusJoined OnActions
+ + + + + - - - - - - + + + + - - -
UserRoleStatusJoinedActions
No users found.
-
- -
+ + + + - + + Current User +
No users found.
-
-
+
+
- \ No newline at end of file diff --git a/assets/css/custom.css b/assets/css/custom.css index efa53a1..f84b682 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -1,195 +1,262 @@ +/* assets/css/custom.css */ + +/* +* MODERN, YOUTHFUL & INVITING AESTHETIC +* +* - Light, neutral base theme with vibrant accent colors. +* - Clear, large typography for a strong visual hierarchy. +* - Comfortably large, prominent interactive elements. +* - Tasteful soft gradients and subtle shadows. +* - Optional glassmorphism for a liquid-glass effect. +*/ + +@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&family=Playfair+Display:wght@700&display=swap'); + :root { - --primary-color: #0f172a; - --accent-color: #ea580c; - --bg-color: #f8fafc; - --surface-color: #ffffff; - --text-color: #1e293b; - --text-muted: #64748b; - --border-color: #e2e8f0; - --card-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); -} - -body.dark-mode { - --primary-color: #f8fafc; - --accent-color: #f97316; - --bg-color: #0f172a; - --surface-color: #1e293b; - --text-color: #f1f5f9; - --text-muted: #94a3b8; - --border-color: #334155; - --card-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.5); + /* Color Palette */ + --primary-color: #4f46e5; /* Vibrant Indigo */ + --secondary-color: #10b981; /* Bright Emerald */ + --accent-color: #ec4899; /* Hot Pink */ + --bg-color: #f9fafb; /* Very Light Gray */ + --surface-color: #ffffff; /* White */ + --text-color: #1f2937; /* Dark Gray */ + --text-muted: #6b7280; /* Medium Gray */ + --border-color: #e5e7eb; /* Light Gray */ + + /* Typography */ + --font-primary: 'Poppins', sans-serif; + --font-display: 'Playfair Display', serif; + + /* Effects */ + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + --border-radius: 0.75rem; /* 12px */ } +/* Base & Typography */ body { - font-family: 'Inter', system-ui, -apple-system, sans-serif; + font-family: var(--font-primary); background-color: var(--bg-color); color: var(--text-color); - transition: background-color 0.3s ease, color 0.3s ease; + line-height: 1.6; } +h1, h2, h3, h4, h5, h6 { + font-family: var(--font-display); + color: var(--primary-color); + font-weight: 700; +} + +h1 { font-size: 3rem; } +h2 { font-size: 2.25rem; } +h3 { font-size: 1.875rem; } + +a { + color: var(--primary-color); + text-decoration: none; + transition: color 0.3s ease; +} + +a:hover { + color: var(--accent-color); +} + +/* Layout & Containers */ +.container { + max-width: 1200px; +} + +.section-padding { + padding: 6rem 0; +} + +/* Navbar */ .navbar { - background-color: rgba(255, 255, 255, 0.95); + background: rgba(255, 255, 255, 0.7); backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); border-bottom: 1px solid var(--border-color); -} - -body.dark-mode .navbar { - background-color: rgba(15, 23, 42, 0.95); - border-bottom: 1px solid var(--border-color); + box-shadow: var(--shadow-sm); + padding: 1rem 0; } .navbar-brand { + font-family: var(--font-display); font-weight: 700; - color: var(--text-color) !important; + font-size: 1.75rem; + color: var(--primary-color) !important; } .nav-link { + font-weight: 600; color: var(--text-muted) !important; - font-weight: 500; + padding: 0.5rem 1rem !important; + border-radius: var(--border-radius); + transition: all 0.3s ease; } .nav-link:hover, .nav-link.active { color: var(--accent-color) !important; + background-color: rgba(236, 72, 153, 0.1); } -.hero-section { - position: relative; - padding: 8rem 0 6rem; - background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%); - color: white; - border-radius: 0 0 2rem 2rem; - margin-bottom: 4rem; - overflow: hidden; -} - -.hero-bg-overlay { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-image: url('https://images.pexels.com/photos/120049/pexels-photo-120049.jpeg?auto=compress&cs=tinysrgb&w=1600'); - background-size: cover; - background-position: center; - opacity: 0.2; - z-index: 0; -} - -.hero-content { - position: relative; - z-index: 1; -} - -.search-card { - background-color: var(--surface-color); - border-radius: 1rem; - padding: 1.5rem; - box-shadow: 0 10px 25px -5px rgb(0 0 0 / 0.1); - margin-top: 2rem; - border: 1px solid var(--border-color); -} - -.form-control, .form-select { - background-color: var(--bg-color); - border: 1px solid var(--border-color); - color: var(--text-color); - padding: 0.75rem 1rem; - border-radius: 0.5rem; -} - -.form-control:focus, .form-select:focus { - background-color: var(--bg-color); - border-color: var(--accent-color); - color: var(--text-color); - box-shadow: 0 0 0 2px rgba(234, 88, 12, 0.2); +/* Buttons */ +.btn { + font-weight: 600; + padding: 0.8rem 2rem; + border-radius: var(--border-radius); + border: none; + transition: all 0.3s ease; + box-shadow: var(--shadow-md); } .btn-primary { - background-color: var(--accent-color); - border-color: var(--accent-color); - padding: 0.75rem 1.5rem; - font-weight: 600; + background: linear-gradient(45deg, var(--primary-color), var(--secondary-color)); + color: white; } .btn-primary:hover { - background-color: #c2410c; - border-color: #c2410c; + transform: translateY(-2px); + box-shadow: var(--shadow-lg); } -.section-title { - font-weight: 700; - margin-bottom: 2rem; - position: relative; - display: inline-block; -} - -.section-title::after { - content: ''; - position: absolute; - left: 0; - bottom: -10px; - width: 60px; - height: 4px; - background-color: var(--accent-color); - border-radius: 2px; -} - -.car-card { +.btn-secondary { background-color: var(--surface-color); + color: var(--primary-color); border: 1px solid var(--border-color); - border-radius: 1rem; +} + +.btn-secondary:hover { + background-color: var(--bg-color); +} + +/* Forms & Inputs */ +.form-control, .form-select { + padding: 1rem; + border: 1px solid var(--border-color); + border-radius: var(--border-radius); + background-color: var(--surface-color); + transition: all 0.3s ease; + font-size: 1rem; +} + +.form-control:focus, .form-select:focus { + outline: none; + border-color: var(--primary-color); + box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.2); +} + +.form-label { + font-weight: 600; + margin-bottom: 0.5rem; +} + +/* Cards */ +.card { + background-color: var(--surface-color); + border: none; + border-radius: var(--border-radius); + box-shadow: var(--shadow-md); overflow: hidden; - transition: transform 0.3s ease, box-shadow 0.3s ease; + transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); height: 100%; } -.car-card:hover { - transform: translateY(-5px); - box-shadow: var(--card-shadow); +.card:hover { + transform: translateY(-10px); + box-shadow: var(--shadow-lg); } -.car-img-top { - height: 220px; +.card-img-top { + height: 250px; object-fit: cover; } -.car-card-body { - padding: 1.5rem; +.card-body { + padding: 1.75rem; } -.price-tag { - color: var(--accent-color); - font-weight: 700; +.card-title { + font-family: var(--font-primary); + font-weight: 600; font-size: 1.25rem; } -.spec-item { - color: var(--text-muted); - font-size: 0.9rem; +/* Login/Register Form */ +.form-container { display: flex; + justify-content: center; align-items: center; - gap: 0.5rem; + min-height: 100vh; + background: linear-gradient(-45deg, #e0c3fc, #8ec5fc); } -.spec-item i { - color: var(--accent-color); +.auth-card { + width: 100%; + max-width: 450px; + padding: 3rem; } +/* Admin Dashboard */ +.admin-wrapper { + display: flex; +} + +.admin-sidebar { + width: 260px; + background: var(--primary-color); + color: white; + min-height: 100vh; + padding: 2rem 1rem; + position: fixed; + height: 100%; +} + +.admin-sidebar .navbar-brand { + color: white !important; +} + +.admin-sidebar .nav-link { + color: rgba(255, 255, 255, 0.7) !important; + font-weight: 500; +} + +.admin-sidebar .nav-link:hover, .admin-sidebar .nav-link.active { + color: white !important; + background-color: rgba(255, 255, 255, 0.1); +} + +.admin-main-content { + margin-left: 260px; + padding: 2rem; + width: calc(100% - 260px); +} + +.stat-card { + background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); + color: white; + padding: 2rem; + border-radius: var(--border-radius); + margin-bottom: 1.5rem; +} + +.stat-card h4 { + color: white; +} + +/* Footer */ .footer { - background-color: var(--surface-color); - border-top: 1px solid var(--border-color); + background-color: var(--text-color); + color: var(--bg-color); padding: 4rem 0 2rem; margin-top: 4rem; } -/* Dark mode toggler */ -.theme-toggle { - cursor: pointer; - padding: 0.5rem; - border-radius: 50%; - transition: background-color 0.2s; +.footer a { + color: var(--secondary-color); } -.theme-toggle:hover { - background-color: rgba(128, 128, 128, 0.1); +.footer a:hover { + color: var(--accent-color); } \ No newline at end of file diff --git a/assets/js/main.js b/assets/js/main.js index fe5258e..eeaf3e3 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1,27 +1,29 @@ document.addEventListener('DOMContentLoaded', () => { - const themeToggleBtn = document.getElementById('theme-toggle'); - const body = document.body; - const icon = themeToggleBtn.querySelector('i'); + const themeToggle = document.querySelector('.theme-toggle'); - // Check local storage - const currentTheme = localStorage.getItem('theme'); - if (currentTheme === 'dark') { - body.classList.add('dark-mode'); - icon.classList.remove('bi-moon-fill'); - icon.classList.add('bi-sun-fill'); - } + if (themeToggle) { + const body = document.body; + const icon = themeToggle.querySelector('i'); - themeToggleBtn.addEventListener('click', () => { - body.classList.toggle('dark-mode'); - - if (body.classList.contains('dark-mode')) { - localStorage.setItem('theme', 'dark'); + const currentTheme = localStorage.getItem('theme'); + if (currentTheme === 'dark') { + body.classList.add('dark-mode'); icon.classList.remove('bi-moon-fill'); icon.classList.add('bi-sun-fill'); - } else { - localStorage.setItem('theme', 'light'); - icon.classList.remove('bi-sun-fill'); - icon.classList.add('bi-moon-fill'); } - }); -}); + + themeToggle.addEventListener('click', () => { + body.classList.toggle('dark-mode'); + + if (body.classList.contains('dark-mode')) { + localStorage.setItem('theme', 'dark'); + icon.classList.remove('bi-moon-fill'); + icon.classList.add('bi-sun-fill'); + } else { + localStorage.setItem('theme', 'light'); + icon.classList.remove('bi-sun-fill'); + icon.classList.add('bi-moon-fill'); + } + }); + } +}); \ No newline at end of file diff --git a/car_detail.php b/car_detail.php index 6b0c23e..8fb2a74 100644 --- a/car_detail.php +++ b/car_detail.php @@ -1,44 +1,62 @@ prepare("INSERT INTO bookings (user_id, car_id) VALUES (?, ?)"); - $stmt->execute([$userId, $carId]); - $stmt = $pdo->prepare("UPDATE cars SET status = 'reserved' WHERE id = ?"); - $stmt->execute([$carId]); - $booking_success = "Your booking request has been sent!"; -} -// Handle Review -if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['submit_review'])) { - if (!isset($_SESSION['user_id'])) { - header("Location: login.php"); - exit(); + // Booking Logic + if (isset($_POST['book_now'])) { + try { + $pdo->beginTransaction(); + $stmt = $pdo->prepare("INSERT INTO bookings (user_id, car_id, status) VALUES (?, ?, 'pending')"); + $stmt->execute([$userId, $carId]); + $stmt = $pdo->prepare("UPDATE cars SET status = 'reserved' WHERE id = ? AND status = 'approved'"); + $stmt->execute([$carId]); + $pdo->commit(); + $message = "Your booking request has been sent! The car is reserved for you pending admin approval."; + $message_type = 'success'; + } catch (Exception $e) { + $pdo->rollBack(); + error_log("Booking failed: " . $e->getMessage()); + $message = "There was an error processing your booking. Please try again."; + $message_type = 'danger'; + } } - $userId = $_SESSION['user_id']; - $rating = $_POST['rating']; - $review = $_POST['review']; - $stmt = $pdo->prepare("INSERT INTO reviews (car_id, user_id, rating, review) VALUES (?, ?, ?, ?)"); - $stmt->execute([$carId, $userId, $rating, $review]); - $review_success = "Your review has been submitted for approval!"; + // Review Logic + if (isset($_POST['submit_review'])) { + $rating = filter_input(INPUT_POST, 'rating', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1, 'max_range' => 5]]); + $review_text = trim(filter_input(INPUT_POST, 'review', FILTER_SANITIZE_SPECIAL_CHARS)); + + if ($rating && !empty($review_text)) { + $stmt = $pdo->prepare("INSERT INTO reviews (car_id, user_id, rating, review) VALUES (?, ?, ?, ?)"); + $stmt->execute([$carId, $userId, $rating, $review_text]); + $message = "Your review has been submitted and is pending approval."; + $message_type = 'success'; + } else { + $message = "Invalid rating or review text."; + $message_type = 'danger'; + } + } } +// Fetch car details $stmt = $pdo->prepare("SELECT * FROM cars WHERE id = ?"); $stmt->execute([$carId]); $car = $stmt->fetch(PDO::FETCH_ASSOC); @@ -48,7 +66,7 @@ if (!$car) { exit(); } -// Fetch Reviews +// Fetch approved reviews $stmt = $pdo->prepare("SELECT r.*, u.username FROM reviews r JOIN users u ON r.user_id = u.id WHERE r.car_id = ? AND r.status = 'approved' ORDER BY r.created_at DESC"); $stmt->execute([$carId]); $reviews = $stmt->fetchAll(PDO::FETCH_ASSOC); @@ -60,89 +78,117 @@ $projectName = htmlspecialchars($car['make'] . ' ' . $car['model']); - <?= $projectName ?> + <?= $projectName ?> - Car Sells Afghanistan + -
-
-
- <?= $projectName ?> -
-
-

-

-

Year:

-

Price: $

-

Status:

- - -
- +
+
+
+ +
+ <?= $projectName ?> +
- -
- -
- -
-
+ +
+

+

+ +

-
+
+

Key Specifications

+
+
Year:
+
Color:
+
Mileage: km
+
Fuel: Petrol
+
+
- -
-
-

Reviews

- -
- +
+ $ +
- -
-
-
Leave a Review
-
-
- - -
-
- - -
- + +
+ + + + +
-
+ +
This car is currently and cannot be booked.
+
- -

Log in to leave a review.

- +
- -
-
-
-
-

Rating:

-

+
+ + +
+
+

Customer Reviews

+ + +
+
+

Leave Your Feedback

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

Log in to share your experience.

+ + + +

Be the first to write a review for this car!

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

+ +
+
+
+ +
- - -

No reviews yet.

-
-
+
diff --git a/car_list.php b/car_list.php index 5bd3b56..d25ea66 100644 --- a/car_list.php +++ b/car_list.php @@ -1,70 +1,151 @@ = ?"; + $params[] = $_GET['min_price']; +} +if (!empty($_GET['max_price'])) { + $whereClauses[] = "price <= ?"; + $params[] = $_GET['max_price']; +} +if (!empty($_GET['search'])) { + $whereClauses[] = "(model LIKE ? OR description LIKE ?)"; + $searchTerm = "%{$_GET['search']}%"; + $params[] = $searchTerm; + $params[] = $searchTerm; +} + +$sql = "SELECT * FROM cars"; +if (!empty($whereClauses)) { + $sql .= " WHERE " . implode(' AND ', $whereClauses); } $sql .= " ORDER BY created_at DESC"; + $stmt = $pdo->prepare($sql); $stmt->execute($params); $cars = $stmt->fetchAll(PDO::FETCH_ASSOC); -$projectName = 'Available Cars'; +// Fetch distinct makes and provinces for filter dropdowns +$makes = $pdo->query("SELECT DISTINCT make FROM cars WHERE status = 'approved' ORDER BY make ASC")->fetchAll(PDO::FETCH_COLUMN); +$provinces = $pdo->query("SELECT DISTINCT province FROM cars WHERE status = 'approved' AND province IS NOT NULL ORDER BY province ASC")->fetchAll(PDO::FETCH_COLUMN); + +$projectName = 'All Car Listings'; ?> - <?= htmlspecialchars($projectName) ?> + <?= htmlspecialchars($projectName) ?> - Car Sells Afghanistan + -
-

-
-
- -
-
- -
-
+
+
+

Our Vehicle Collection

+

Browse the complete inventory of our certified pre-owned vehicles from all over Afghanistan.

+
+
-
- -
-
- <?= htmlspecialchars($car['make'] . ' ' . $car['model']) ?> -
-
-

-

Price: $

- View Details -
+
+
+ + +
+
+ + +
+
+ <?= htmlspecialchars($car['make'] . ' ' . $car['model']) ?> +
+
+

+
+ + km +
+

$

+ View Details +
+
+
+ + +
+ +
+
- - -
-

No cars found.

-
-
-
+ - + \ No newline at end of file diff --git a/db/setup_cars.php b/db/setup_cars.php index 9f62402..11075bf 100644 --- a/db/setup_cars.php +++ b/db/setup_cars.php @@ -3,76 +3,86 @@ require_once __DIR__ . '/config.php'; try { $pdo = db(); - - // Create cars table - $sql = "CREATE TABLE IF NOT EXISTS cars ( - id INT AUTO_INCREMENT PRIMARY KEY, - make VARCHAR(50) NOT NULL, - model VARCHAR(50) NOT NULL, - year INT NOT NULL, - price DECIMAL(10, 2) NOT NULL, - mileage INT DEFAULT 0, - image_url VARCHAR(255), - description TEXT, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - )"; - $pdo->exec($sql); - echo "Table 'cars' created or already exists.
"; + echo "Connected to database successfully.
"; - // Check if empty + // Idempotently add columns to the cars table if they don't exist + $columns_to_add = [ + 'status' => "ALTER TABLE cars ADD COLUMN status VARCHAR(50) NOT NULL DEFAULT 'pending'", + 'color' => "ALTER TABLE cars ADD COLUMN color VARCHAR(50)", + 'province' => "ALTER TABLE cars ADD COLUMN province VARCHAR(100)", + 'city' => "ALTER TABLE cars ADD COLUMN city VARCHAR(100)", + ]; + + $stmt = $pdo->query("DESCRIBE cars"); + $existing_columns = $stmt->fetchAll(PDO::FETCH_COLUMN); + + foreach ($columns_to_add as $column => $sql) { + if (!in_array($column, $existing_columns)) { + $pdo->exec($sql); + echo "Column '{$column}' added to 'cars' table.
"; + } + } + + // Check if the table is empty before seeding $stmt = $pdo->query("SELECT COUNT(*) FROM cars"); if ($stmt->fetchColumn() == 0) { - // Seed data + echo "Table 'cars' is empty, proceeding with seeding.
"; $cars = [ [ - 'make' => 'Toyota', - 'model' => 'Corolla', - 'year' => 2015, - 'price' => 12500.00, - 'mileage' => 45000, - 'image_url' => 'https://images.pexels.com/photos/170811/pexels-photo-170811.jpeg?auto=compress&cs=tinysrgb&w=800', - 'description' => 'Reliable white Corolla, excellent condition. Kabul plate.' + 'make' => 'Toyota', 'model' => 'Corolla', 'year' => 2018, 'price' => 13500, 'mileage' => 85000, 'color' => 'White', 'province' => 'Kabul', 'city' => 'Kabul', 'status' => 'approved', + 'image_url' => 'https://images.pexels.com/photos/1545743/pexels-photo-1545743.jpeg?auto=compress&cs=tinysrgb&w=800', + 'description' => 'A well-maintained Corolla, perfect for city driving. Economical and reliable.' ], [ - 'make' => 'Toyota', - 'model' => 'Land Cruiser', - 'year' => 2018, - 'price' => 45000.00, - 'mileage' => 32000, - 'image_url' => 'https://images.pexels.com/photos/116675/pexels-photo-116675.jpeg?auto=compress&cs=tinysrgb&w=800', - 'description' => 'Black Land Cruiser V8. Powerful and luxurious. Ready for any road.' + 'make' => 'Toyota', 'model' => 'Land Cruiser', 'year' => 2020, 'price' => 75000, 'mileage' => 45000, 'color' => 'Black', 'province' => 'Herat', 'city' => 'Herat', 'status' => 'approved', + 'image_url' => 'https://images.pexels.com/photos/3764984/pexels-photo-3764984.jpeg?auto=compress&cs=tinysrgb&w=800', + 'description' => 'Powerful V8 Land Cruiser. Armored. Ready for any terrain or situation.' ], [ - 'make' => 'Mercedes-Benz', - 'model' => 'C-Class', - 'year' => 2012, - 'price' => 18000.00, - 'mileage' => 60000, - 'image_url' => 'https://images.pexels.com/photos/112460/pexels-photo-112460.jpeg?auto=compress&cs=tinysrgb&w=800', - 'description' => 'Silver C-Class. Imported from Germany. Clean interior.' + 'make' => 'Mercedes-Benz', 'model' => 'C200', 'year' => 2016, 'price' => 22000, 'mileage' => 72000, 'color' => 'Silver', 'province' => 'Balkh', 'city' => 'Mazar-i-Sharif', 'status' => 'approved', + 'image_url' => 'https://images.pexels.com/photos/241316/pexels-photo-241316.jpeg?auto=compress&cs=tinysrgb&w=800', + 'description' => 'German luxury and comfort. Smooth ride with a clean interior. -3 plate number.' ], [ - 'make' => 'Toyota', - 'model' => 'Hilux', - 'year' => 2020, - 'price' => 28000.00, - 'mileage' => 15000, + 'make' => 'Toyota', 'model' => 'Hilux', 'year' => 2021, 'price' => 35000, 'mileage' => 25000, 'color' => 'Red', 'province' => 'Kandahar', 'city' => 'Kandahar', 'status' => 'approved', + 'image_url' => 'https://images.pexels.com/photos/248747/pexels-photo-248747.jpeg?auto=compress&cs=tinysrgb&w=800', + 'description' => 'Robust and versatile Hilux pickup. Excellent for both work and family.' + ], + [ + 'make' => 'Honda', 'model' => 'Civic', 'year' => 2019, 'price' => 17000, 'mileage' => 55000, 'color' => 'Blue', 'province' => 'Kabul', 'city' => 'Kabul', 'status' => 'approved', + 'image_url' => 'https://images.pexels.com/photos/1637859/pexels-photo-1637859.jpeg?auto=compress&cs=tinysrgb&w=800', + 'description' => 'Sporty and modern Honda Civic. Features a sunroof and great fuel economy.' + ], + [ + 'make' => 'Ford', 'model' => 'Ranger', 'year' => 2017, 'price' => 24000, 'mileage' => 95000, 'color' => 'Gray', 'province' => 'Nangarhar', 'city' => 'Jalalabad', 'status' => 'pending', 'image_url' => 'https://images.pexels.com/photos/119435/pexels-photo-119435.jpeg?auto=compress&cs=tinysrgb&w=800', - 'description' => 'Double cabin Hilux. Strong engine, barely used.' - ] + 'description' => 'American toughness. This Ford Ranger is built to handle tough jobs.' + ], + [ + 'make' => 'Toyota', 'model' => 'RAV4', 'year' => 2018, 'price' => 21000, 'mileage' => 62000, 'color' => 'White', 'province' => 'Herat', 'city' => 'Herat', 'status' => 'approved', + 'image_url' => 'https://images.pexels.com/photos/707046/pexels-photo-707046.jpeg?auto=compress&cs=tinysrgb&w=800', + 'description' => 'Family-friendly SUV. Spacious and comfortable for long journeys.' + ], + [ + 'make' => 'Lexus', 'model' => 'LX 570', 'year' => 2019, 'price' => 85000, 'mileage' => 40000, 'color' => 'Pearl White', 'province' => 'Kabul', 'city' => 'Kabul', 'status' => 'approved', + 'image_url' => 'https://images.pexels.com/photos/116675/pexels-photo-116675.jpeg?auto=compress&cs=tinysrgb&w=800', + 'description' => 'The pinnacle of luxury and capability. Top-of-the-line model with all options.' + ], ]; - $insertSql = "INSERT INTO cars (make, model, year, price, mileage, image_url, description) VALUES (:make, :model, :year, :price, :mileage, :image_url, :description)"; + $insertSql = "INSERT INTO cars (make, model, year, price, mileage, color, province, city, status, image_url, description) VALUES (:make, :model, :year, :price, :mileage, :color, :province, :city, :status, :image_url, :description)"; $stmt = $pdo->prepare($insertSql); + $count = 0; foreach ($cars as $car) { $stmt->execute($car); + $count++; } - echo "Seeded " . count($cars) . " cars.
"; + echo "Seeded " . $count . " cars into the database.
"; } else { - echo "Table 'cars' already has data.
"; + echo "Table 'cars' already contains data. No seeding performed.
"; } } catch (PDOException $e) { - echo "Error: " . $e->getMessage(); -} + echo "Database error: " . $e->getMessage(); +} \ No newline at end of file diff --git a/db/setup_users.php b/db/setup_users.php index 7308721..2919322 100644 --- a/db/setup_users.php +++ b/db/setup_users.php @@ -16,16 +16,28 @@ try { $pdo->exec($sql); echo "Table 'users' created successfully." . PHP_EOL; - // Add a default admin user if one doesn't exist - $stmt = $pdo->prepare("SELECT COUNT(*) FROM users WHERE email = :email"); - $stmt->execute(['email' => 'admin@example.com']); - if ($stmt->fetchColumn() == 0) { - $username = 'admin'; - $email = 'admin@example.com'; - $password = 'password'; // In a real app, use a stronger password and handle this securely - $password_hash = password_hash($password, PASSWORD_DEFAULT); - $role = 'admin'; + // Add or update the admin user + $username = 'admin'; + $email = 'admin@admin.com'; + $password = '123'; + $password_hash = password_hash($password, PASSWORD_DEFAULT); + $role = 'admin'; + $stmt = $pdo->prepare("SELECT COUNT(*) FROM users WHERE username = :username"); + $stmt->execute([':username' => $username]); + + if ($stmt->fetchColumn() > 0) { + // User exists, update password and email + $update_sql = "UPDATE users SET password_hash = :password_hash, email = :email WHERE username = :username"; + $update_stmt = $pdo->prepare($update_sql); + $update_stmt->execute([ + ':password_hash' => $password_hash, + ':email' => $email, + ':username' => $username + ]); + echo "Admin user updated with new password." . PHP_EOL; + } else { + // User does not exist, insert new admin user $insert_sql = " INSERT INTO users (username, email, password_hash, role) VALUES (:username, :email, :password_hash, :role); @@ -37,10 +49,9 @@ try { ':password_hash' => $password_hash, ':role' => $role ]); - echo "Default admin user created (admin@example.com / password)." . PHP_EOL; + echo "Default admin user created (admin / 123)." . PHP_EOL; } - } catch (PDOException $e) { die("DB ERROR: " . $e->getMessage()); -} +} \ No newline at end of file diff --git a/index.php b/index.php index e6ec284..6d09491 100644 --- a/index.php +++ b/index.php @@ -6,14 +6,15 @@ require_once __DIR__ . '/db/config.php'; $cars = []; try { $pdo = db(); - $stmt = $pdo->query("SELECT * FROM cars ORDER BY created_at DESC LIMIT 4"); + // Fetch more cars to showcase the design + $stmt = $pdo->query("SELECT * FROM cars WHERE status = 'approved' ORDER BY created_at DESC LIMIT 8"); $cars = $stmt->fetchAll(PDO::FETCH_ASSOC); } catch (Exception $e) { - // Graceful fallback if DB fails (shouldn't happen given the setup step) - error_log($e->getMessage()); + error_log("DB Error: " . $e->getMessage()); + // You can set a user-friendly error message here if you want } -// Meta variables +// Meta variables from environment - important for platform integration $projectName = getenv('PROJECT_NAME') ?: 'Car Sells in Afghanistan'; $projectDesc = getenv('PROJECT_DESCRIPTION') ?: 'The best marketplace for buying and selling cars in Afghanistan.'; $projectImage = getenv('PROJECT_IMAGE_URL') ?: 'https://images.pexels.com/photos/120049/pexels-photo-120049.jpeg?auto=compress&cs=tinysrgb&w=1200'; @@ -23,21 +24,18 @@ $projectImage = getenv('PROJECT_IMAGE_URL') ?: 'https://images.pexels.com/photos - <?php echo htmlspecialchars($projectName); ?> + <?php echo htmlspecialchars($projectName); ?> - Modern Car Marketplace - + - + - - - - + @@ -46,91 +44,81 @@ $projectImage = getenv('PROJECT_IMAGE_URL') ?: 'https://images.pexels.com/photos -
-
-
-
-
-

Find Your Dream Car

-

The most trusted marketplace for buying and selling cars in Afghanistan.

- -
-
-
- - +
+
+

Your Dream Car Awaits

+

Discover the best deals on new and used cars in Afghanistan. Quality, trust, and transparency guaranteed.

+ Explore Cars + View All Listings +
+
+ + + + + +
+
+
+

How It Works

+

A simple, transparent, and secure process.

+
+
+
+
+ +

1. Find Your Car

+

Browse our curated selection of high-quality vehicles. Use filters to narrow down your perfect match.

+
+
+
+
+ +

2. Book a Test Drive

+

Schedule a test drive online. We'll confirm your appointment and prepare the vehicle for you.

+
+
+
+
+ +

3. Secure Your Deal

+

Finalize your purchase with our secure payment system and drive away in your new car with confidence.

- -
-
-

Featured Cars

- View All -
- -
- - -
-
-
- <?php echo htmlspecialchars($car['make'] . ' ' . $car['model']); ?> - -
-
-
-

- -
-
-
km
-
Petrol
-
-
- $ - Details -
-
-
-
-
- - -
-

No cars available at the moment. Please check back later.

-
- -
-
- - - \ No newline at end of file diff --git a/login.php b/login.php index d8d7a80..f2f3e9d 100644 --- a/login.php +++ b/login.php @@ -4,20 +4,21 @@ require_once 'db/config.php'; session_start(); if (isset($_SESSION['user_id'])) { - header("Location: index.php"); + // Redirect to dashboard if already logged in + header("Location: dashboard.php"); exit(); } $errors = []; if ($_SERVER['REQUEST_METHOD'] === 'POST') { - $email = trim($_POST['email']); - $password = $_POST['password']; + $email = trim($_POST['email'] ?? ''); + $password = $_POST['password'] ?? ''; - if (empty($email)) { - $errors[] = 'Email is required'; + if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) { + $errors[] = 'A valid email is required.'; } if (empty($password)) { - $errors[] = 'Password is required'; + $errors[] = 'Password is required.'; } if (empty($errors)) { @@ -31,13 +32,20 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $_SESSION['user_id'] = $user['id']; $_SESSION['username'] = $user['username']; $_SESSION['role'] = $user['role']; - header("Location: index.php"); + // Redirect to the appropriate dashboard + if ($user['role'] === 'admin') { + header("Location: admin/index.php"); + } else { + header("Location: dashboard.php"); + } exit(); } else { - $errors[] = 'Invalid email or password'; + $errors[] = 'Invalid email or password combination.'; } } catch (PDOException $e) { - $errors[] = "Database error: " . $e->getMessage(); + // For security, don't show detailed DB errors in production + error_log("Database error: " . $e->getMessage()); + $errors[] = "An internal error occurred. Please try again later."; } } } @@ -48,46 +56,50 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { Login - Car Sells in Afghanistan - + - - - - -
-
-

Login to Your Account

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

- Don't have an account? Register here. -

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

+ Don't have an account? Create one now. +

+

+ Back to Home +

+
- +
- - - + \ No newline at end of file diff --git a/partials/footer.php b/partials/footer.php index 23a91aa..af47af8 100644 --- a/partials/footer.php +++ b/partials/footer.php @@ -1,13 +1,40 @@ -