diff --git a/admin/bookings.php b/admin/bookings.php new file mode 100644 index 0000000..c316d10 --- /dev/null +++ b/admin/bookings.php @@ -0,0 +1,104 @@ +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]); + } + header("Location: bookings.php"); + exit(); +} + +$bookings = $pdo->query(" + SELECT b.id, b.status, b.booking_date, u.username, 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 + ORDER BY b.booking_date DESC +")->fetchAll(PDO::FETCH_ASSOC); + +$projectName = 'Manage Bookings'; +?> + + + + + + <?= htmlspecialchars($projectName) ?> + + + + + + +
+
+ +
+
+

Manage Bookings

+
+
+
+
+ + + + + + + + + + + + + + + +
UserCarDateStatusActions
+ +
+ + + + +
+ +
+
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/admin/cars.php b/admin/cars.php new file mode 100644 index 0000000..534bc3a --- /dev/null +++ b/admin/cars.php @@ -0,0 +1,200 @@ +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']; + $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); + $fileName = uniqid() . '-' . basename($_FILES["image"]["name"]); + $targetFilePath = $targetDir . $fileName; + if (move_uploaded_file($_FILES["image"]["tmp_name"], $targetFilePath)) { + $imageUrl = 'assets/images/cars/' . $fileName; + } + } + + if ($carId) { // Update + $sql = "UPDATE cars SET make=?, model=?, year=?, price=?, status=?, description=?, image_url=? WHERE id=?"; + $stmt = $pdo->prepare($sql); + $stmt->execute([$make, $model, $year, $price, $status, $description, $imageUrl, $carId]); + } else { // Insert + $sql = "INSERT INTO cars (make, model, year, price, status, description, image_url) VALUES (?, ?, ?, ?, ?, ?, ?)"; + $stmt = $pdo->prepare($sql); + $stmt->execute([$make, $model, $year, $price, $status, $description, $imageUrl]); + } + header("Location: cars.php"); + exit(); +} + +$search = $_GET['search'] ?? ''; +$filter_status = $_GET['status'] ?? 'all'; + +$sql = "SELECT * FROM cars"; +$params = []; +if (!empty($search)) { + $sql .= " WHERE (make LIKE ? OR model LIKE ?)"; + $params[] = "%$search%"; + $params[] = "%$search%"; +} +if ($filter_status !== 'all') { + $sql .= (strpos($sql, 'WHERE') === false ? " WHERE" : " AND") . " status = ?"; + $params[] = $filter_status; +} +$sql .= " ORDER BY created_at DESC"; +$stmt = $pdo->prepare($sql); +$stmt->execute($params); +$cars = $stmt->fetchAll(PDO::FETCH_ASSOC); + +$projectName = 'Manage Cars'; +?> + + + + + + <?php echo htmlspecialchars($projectName); ?> + + + + + + + +
+
+ +
+
+

Manage Cars

+ +
+ +
+
+
+
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + + + +
ImageMake/ModelYearPriceStatusActions
<?= htmlspecialchars($car['make']) ?>$ + +
+ + +
+
+
+
+
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/admin/index.php b/admin/index.php new file mode 100644 index 0000000..1ee505e --- /dev/null +++ b/admin/index.php @@ -0,0 +1,120 @@ + $pdo->query("SELECT COUNT(*) FROM users")->fetchColumn(), + 'cars' => $pdo->query("SELECT COUNT(*) FROM cars")->fetchColumn(), + 'bookings' => $pdo->query("SELECT COUNT(*) FROM bookings")->fetchColumn(), + 'reviews' => $pdo->query("SELECT COUNT(*) FROM reviews")->fetchColumn(), +]; + +// Chart Data +$sales_data = $pdo->query("SELECT DATE(booking_date) as date, COUNT(*) as count FROM bookings WHERE status = 'approved' GROUP BY DATE(booking_date) ORDER BY date ASC")->fetchAll(PDO::FETCH_ASSOC); +$bookings_status_data = $pdo->query("SELECT status, COUNT(*) as count FROM bookings GROUP BY status")->fetchAll(PDO::FETCH_ASSOC); + +// Top Cars +$top_selling_cars = $pdo->query(" + SELECT c.make, c.model, COUNT(b.id) as sales + FROM cars c + JOIN bookings b ON c.id = b.car_id + WHERE b.status = 'approved' + GROUP BY c.id + ORDER BY sales DESC + LIMIT 5 +")->fetchAll(PDO::FETCH_ASSOC); + +$most_popular_cars = $pdo->query(" + SELECT c.make, c.model, COUNT(r.id) as review_count + FROM cars c + LEFT JOIN reviews r ON c.id = r.car_id + GROUP BY c.id + ORDER BY review_count DESC + LIMIT 5 +")->fetchAll(PDO::FETCH_ASSOC); + +$projectName = 'Admin Dashboard'; +?> + + + + + + <?= htmlspecialchars($projectName) ?> + + + + + + + +
+
+ +
+

Dashboard

+
+ +
Users

+
Cars

+
Bookings

+
Reviews

+ + +
Sales Over Time
+
Bookings Status
+ + +
Top Selling Cars
    {$c['make']} {$c['model']} {$c['sales']} sales"; ?>
+
Most Popular Cars (by Reviews)
    {$c['make']} {$c['model']} {$c['review_count']} reviews"; ?>
+
+
+
+
+ + + + \ No newline at end of file diff --git a/admin/reviews.php b/admin/reviews.php new file mode 100644 index 0000000..06361a8 --- /dev/null +++ b/admin/reviews.php @@ -0,0 +1,102 @@ +prepare("UPDATE reviews SET status = 'approved' WHERE id = ?")->execute([$reviewId]); + } elseif (isset($_POST['delete'])) { + $pdo->prepare("DELETE FROM reviews WHERE id = ?")->execute([$reviewId]); + } + header("Location: reviews.php"); + exit(); +} + +$reviews = $pdo->query(" + SELECT r.id, r.rating, r.review, r.status, r.created_at, u.username, c.make, c.model + FROM reviews r + JOIN users u ON r.user_id = u.id + JOIN cars c ON r.car_id = c.id + ORDER BY r.created_at DESC +")->fetchAll(PDO::FETCH_ASSOC); + +$projectName = 'Manage Reviews'; +?> + + + + + + <?= htmlspecialchars($projectName) ?> + + + + + + +
+
+ +
+
+

Manage Reviews

+
+
+
+
+ + + + + + + + + + + + + + + + + +
UserCarRatingReviewDateStatusActions
+
+ + + + + +
+
+
+
+
+
+
+
+ + + diff --git a/admin/users.php b/admin/users.php new file mode 100644 index 0000000..70f00df --- /dev/null +++ b/admin/users.php @@ -0,0 +1,159 @@ +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]); + } + header("Location: users.php"); + exit(); +} + +$search = $_GET['search'] ?? ''; +$filter = $_GET['filter'] ?? 'all'; + +try { + $pdo = db(); + $sql = "SELECT id, username, email, role, created_at, status FROM users"; + $params = []; + + 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 = []; +} + +$projectName = 'Manage Users'; +?> + + + + + + <?php echo htmlspecialchars($projectName); ?> + + + + + + + + + +
+
+ + +
+
+

Manage Users

+
+ +
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IDUsernameEmailRoleStatusJoined OnActions
+
+ + + +
+
No users found.
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/assets/css/custom.css b/assets/css/custom.css index 65a1626..efa53a1 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -1,346 +1,195 @@ :root { - --color-bg: #ffffff; - --color-text: #1a1a1a; - --color-primary: #2563EB; /* Vibrant Blue */ - --color-secondary: #000000; - --color-accent: #A3E635; /* Lime Green */ - --color-surface: #f8f9fa; - --font-heading: 'Space Grotesk', sans-serif; - --font-body: 'Inter', sans-serif; - --border-width: 2px; - --shadow-hard: 5px 5px 0px #000; - --shadow-hover: 8px 8px 0px #000; - --radius-pill: 50rem; - --radius-card: 1rem; + --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); } body { - font-family: var(--font-body); - background-color: var(--color-bg); - color: var(--color-text); - overflow-x: hidden; + font-family: 'Inter', system-ui, -apple-system, sans-serif; + background-color: var(--bg-color); + color: var(--text-color); + transition: background-color 0.3s ease, color 0.3s ease; } -h1, h2, h3, h4, h5, h6, .navbar-brand { - font-family: var(--font-heading); - letter-spacing: -0.03em; -} - -/* Utilities */ -.text-primary { color: var(--color-primary) !important; } -.bg-black { background-color: #000 !important; } -.text-white { color: #fff !important; } -.shadow-hard { box-shadow: var(--shadow-hard); } -.border-2-black { border: var(--border-width) solid #000; } -.py-section { padding-top: 5rem; padding-bottom: 5rem; } - -/* Navbar */ .navbar { - background: rgba(255, 255, 255, 0.9); + background-color: rgba(255, 255, 255, 0.95); backdrop-filter: blur(10px); - border-bottom: var(--border-width) solid transparent; - transition: all 0.3s; - padding-top: 1rem; - padding-bottom: 1rem; + border-bottom: 1px solid var(--border-color); } -.navbar.scrolled { - border-bottom-color: #000; - padding-top: 0.5rem; - padding-bottom: 0.5rem; +body.dark-mode .navbar { + background-color: rgba(15, 23, 42, 0.95); + border-bottom: 1px solid var(--border-color); } -.brand-text { - font-size: 1.5rem; - font-weight: 800; +.navbar-brand { + font-weight: 700; + color: var(--text-color) !important; } .nav-link { + color: var(--text-muted) !important; font-weight: 500; - color: var(--color-text); - margin-left: 1rem; - position: relative; } .nav-link:hover, .nav-link.active { - color: var(--color-primary); + color: var(--accent-color) !important; } -/* Buttons */ -.btn { - font-weight: 700; - font-family: var(--font-heading); - padding: 0.8rem 2rem; - border-radius: var(--radius-pill); - border: var(--border-width) solid #000; - transition: all 0.2s cubic-bezier(0.25, 1, 0.5, 1); - box-shadow: var(--shadow-hard); -} - -.btn:hover { - transform: translate(-2px, -2px); - box-shadow: var(--shadow-hover); -} - -.btn:active { - transform: translate(2px, 2px); - box-shadow: 0 0 0 #000; -} - -.btn-primary { - background-color: var(--color-primary); - border-color: #000; - color: #fff; -} - -.btn-primary:hover { - background-color: #1d4ed8; - border-color: #000; - color: #fff; -} - -.btn-outline-dark { - background-color: #fff; - color: #000; -} - -.btn-cta { - background-color: var(--color-accent); - color: #000; -} - -.btn-cta:hover { - background-color: #8cc629; - color: #000; -} - -/* Hero Section */ .hero-section { - min-height: 100vh; - padding-top: 80px; + 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; } -.background-blob { +.hero-bg-overlay { position: absolute; - border-radius: 50%; - filter: blur(80px); - opacity: 0.6; + 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; } -.blob-1 { - top: -10%; - right: -10%; - width: 600px; - height: 600px; - background: radial-gradient(circle, var(--color-accent), transparent); +.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); } -.blob-2 { - bottom: 10%; - left: -10%; - width: 500px; - height: 500px; - background: radial-gradient(circle, var(--color-primary), transparent); -} - -.highlight-text { - background: linear-gradient(120deg, transparent 0%, transparent 40%, var(--color-accent) 40%, var(--color-accent) 100%); - background-repeat: no-repeat; - background-size: 100% 40%; - background-position: 0 88%; - padding: 0 5px; -} - -.dot { color: var(--color-primary); } - -.badge-pill { - display: inline-block; - padding: 0.5rem 1rem; - border: 2px solid #000; - border-radius: 50px; - font-weight: 700; - background: #fff; - box-shadow: 4px 4px 0 #000; - font-family: var(--font-heading); - font-size: 0.9rem; -} - -/* Marquee */ -.marquee-container { - overflow: hidden; - white-space: nowrap; - border-top: 2px solid #000; - border-bottom: 2px solid #000; -} - -.rotate-divider { - transform: rotate(-2deg) scale(1.05); - z-index: 10; - position: relative; - margin-top: -50px; - margin-bottom: 30px; -} - -.marquee-content { - display: inline-block; - animation: marquee 20s linear infinite; - font-family: var(--font-heading); - font-weight: 700; - font-size: 1.5rem; - letter-spacing: 2px; -} - -@keyframes marquee { - 0% { transform: translateX(0); } - 100% { transform: translateX(-50%); } -} - -/* Portfolio Cards */ -.project-card { - border: 2px solid #000; - border-radius: var(--radius-card); - overflow: hidden; - background: #fff; - transition: transform 0.3s ease; - box-shadow: var(--shadow-hard); - height: 100%; - display: flex; - flex-direction: column; -} - -.project-card:hover { - transform: translateY(-10px); - box-shadow: 8px 8px 0 #000; -} - -.card-img-holder { - height: 250px; - display: flex; - align-items: center; - justify-content: center; - border-bottom: 2px solid #000; - position: relative; - font-size: 4rem; -} - -.placeholder-art { - transition: transform 0.3s ease; -} - -.project-card:hover .placeholder-art { - transform: scale(1.2) rotate(10deg); -} - -.bg-soft-blue { background-color: #e0f2fe; } -.bg-soft-green { background-color: #dcfce7; } -.bg-soft-purple { background-color: #f3e8ff; } -.bg-soft-yellow { background-color: #fef9c3; } - -.category-tag { - position: absolute; - top: 15px; - right: 15px; - background: #000; - color: #fff; - padding: 5px 12px; - border-radius: 20px; - font-size: 0.75rem; - font-weight: 700; -} - -.card-body { padding: 1.5rem; } - -.link-arrow { - text-decoration: none; - color: #000; - font-weight: 700; - display: inline-flex; - align-items: center; - margin-top: auto; -} - -.link-arrow i { transition: transform 0.2s; margin-left: 5px; } -.link-arrow:hover i { transform: translateX(5px); } - -/* About */ -.about-image-stack { - position: relative; - height: 400px; - width: 100%; -} - -.stack-card { - position: absolute; - width: 80%; - height: 100%; - border-radius: var(--radius-card); - border: 2px solid #000; - box-shadow: var(--shadow-hard); - left: 10%; - transform: rotate(-3deg); - background-size: cover; -} - -/* Forms */ -.form-control { - border: 2px solid #000; +.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; - padding: 1rem; - font-weight: 500; - background: #f8f9fa; } -.form-control:focus { - box-shadow: 4px 4px 0 var(--color-primary); - border-color: #000; - background: #fff; +.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); } -/* Animations */ -.animate-up { - opacity: 0; - transform: translateY(30px); - animation: fadeUp 0.8s ease forwards; +.btn-primary { + background-color: var(--accent-color); + border-color: var(--accent-color); + padding: 0.75rem 1.5rem; + font-weight: 600; } -.delay-100 { animation-delay: 0.1s; } -.delay-200 { animation-delay: 0.2s; } - -@keyframes fadeUp { - to { - opacity: 1; - transform: translateY(0); - } +.btn-primary:hover { + background-color: #c2410c; + border-color: #c2410c; } -/* Social */ -.social-links a { - transition: transform 0.2s; +.section-title { + font-weight: 700; + margin-bottom: 2rem; + position: relative; display: inline-block; } -.social-links a:hover { - transform: scale(1.2) rotate(10deg); - color: var(--color-accent) !important; + +.section-title::after { + content: ''; + position: absolute; + left: 0; + bottom: -10px; + width: 60px; + height: 4px; + background-color: var(--accent-color); + border-radius: 2px; } -/* Responsive */ -@media (max-width: 991px) { - .rotate-divider { - transform: rotate(0); - margin-top: 0; - margin-bottom: 2rem; - } - - .hero-section { - padding-top: 120px; - text-align: center; - min-height: auto; - padding-bottom: 100px; - } - - .display-1 { font-size: 3.5rem; } - - .blob-1 { width: 300px; height: 300px; right: -20%; } - .blob-2 { width: 300px; height: 300px; left: -20%; } +.car-card { + background-color: var(--surface-color); + border: 1px solid var(--border-color); + border-radius: 1rem; + overflow: hidden; + transition: transform 0.3s ease, box-shadow 0.3s ease; + height: 100%; } + +.car-card:hover { + transform: translateY(-5px); + box-shadow: var(--card-shadow); +} + +.car-img-top { + height: 220px; + object-fit: cover; +} + +.car-card-body { + padding: 1.5rem; +} + +.price-tag { + color: var(--accent-color); + font-weight: 700; + font-size: 1.25rem; +} + +.spec-item { + color: var(--text-muted); + font-size: 0.9rem; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.spec-item i { + color: var(--accent-color); +} + +.footer { + background-color: var(--surface-color); + border-top: 1px solid var(--border-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; +} + +.theme-toggle:hover { + background-color: rgba(128, 128, 128, 0.1); +} \ No newline at end of file diff --git a/assets/js/main.js b/assets/js/main.js index fdf2cfd..fe5258e 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1,73 +1,27 @@ document.addEventListener('DOMContentLoaded', () => { - - // Smooth scrolling for navigation links - document.querySelectorAll('a[href^="#"]').forEach(anchor => { - anchor.addEventListener('click', function (e) { - e.preventDefault(); - const targetId = this.getAttribute('href'); - if (targetId === '#') return; - - const targetElement = document.querySelector(targetId); - if (targetElement) { - // Close mobile menu if open - const navbarToggler = document.querySelector('.navbar-toggler'); - const navbarCollapse = document.querySelector('.navbar-collapse'); - if (navbarCollapse.classList.contains('show')) { - navbarToggler.click(); - } + const themeToggleBtn = document.getElementById('theme-toggle'); + const body = document.body; + const icon = themeToggleBtn.querySelector('i'); - // Scroll with offset - const offset = 80; - const elementPosition = targetElement.getBoundingClientRect().top; - const offsetPosition = elementPosition + window.pageYOffset - offset; + // 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'); + } - window.scrollTo({ - top: offsetPosition, - behavior: "smooth" - }); - } - }); - }); - - // Navbar scroll effect - const navbar = document.querySelector('.navbar'); - window.addEventListener('scroll', () => { - if (window.scrollY > 50) { - navbar.classList.add('scrolled', 'shadow-sm', 'bg-white'); - navbar.classList.remove('bg-transparent'); + themeToggleBtn.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 { - navbar.classList.remove('scrolled', 'shadow-sm', 'bg-white'); - navbar.classList.add('bg-transparent'); + localStorage.setItem('theme', 'light'); + icon.classList.remove('bi-sun-fill'); + icon.classList.add('bi-moon-fill'); } }); - - // Intersection Observer for fade-up animations - const observerOptions = { - threshold: 0.1, - rootMargin: "0px 0px -50px 0px" - }; - - const observer = new IntersectionObserver((entries) => { - entries.forEach(entry => { - if (entry.isIntersecting) { - entry.target.classList.add('animate-up'); - entry.target.style.opacity = "1"; - observer.unobserve(entry.target); // Only animate once - } - }); - }, observerOptions); - - // Select elements to animate (add a class 'reveal' to them in HTML if not already handled by CSS animation) - // For now, let's just make sure the hero animations run. - // If we want scroll animations, we'd add opacity: 0 to elements in CSS and reveal them here. - // Given the request, the CSS animation I added runs on load for Hero. - // Let's make the project cards animate in. - - const projectCards = document.querySelectorAll('.project-card'); - projectCards.forEach((card, index) => { - card.style.opacity = "0"; - card.style.animationDelay = `${index * 0.1}s`; - observer.observe(card); - }); - -}); \ No newline at end of file +}); diff --git a/car_detail.php b/car_detail.php new file mode 100644 index 0000000..6b0c23e --- /dev/null +++ b/car_detail.php @@ -0,0 +1,150 @@ +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(); + } + $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!"; +} + +$stmt = $pdo->prepare("SELECT * FROM cars WHERE id = ?"); +$stmt->execute([$carId]); +$car = $stmt->fetch(PDO::FETCH_ASSOC); + +if (!$car) { + header("Location: car_list.php"); + exit(); +} + +// Fetch 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); + +$projectName = htmlspecialchars($car['make'] . ' ' . $car['model']); +?> + + + + + + <?= $projectName ?> + + + + + + +
+
+
+ <?= $projectName ?> +
+
+

+

+

Year:

+

Price: $

+

Status:

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

Reviews

+ +
+ + + +
+
+
Leave a Review
+
+
+ + +
+
+ + +
+ +
+
+
+ +

Log in to leave a review.

+ + + +
+
+
-
+

Rating:

+

+
+
+ + +

No reviews yet.

+ +
+
+
+ + + + + \ No newline at end of file diff --git a/car_list.php b/car_list.php new file mode 100644 index 0000000..5bd3b56 --- /dev/null +++ b/car_list.php @@ -0,0 +1,70 @@ +prepare($sql); +$stmt->execute($params); +$cars = $stmt->fetchAll(PDO::FETCH_ASSOC); + +$projectName = 'Available Cars'; +?> + + + + + + <?= htmlspecialchars($projectName) ?> + + + + + + +
+

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

+

Price: $

+ View Details +
+
+
+ + +
+

No cars found.

+
+ +
+
+ + + + + diff --git a/dashboard.php b/dashboard.php new file mode 100644 index 0000000..0abbc9d --- /dev/null +++ b/dashboard.php @@ -0,0 +1,102 @@ +prepare("SELECT * FROM users WHERE id = :id"); + $stmt->execute(['id' => $_SESSION['user_id']]); + $user = $stmt->fetch(PDO::FETCH_ASSOC); +} catch (PDOException $e) { + error_log("Dashboard Page Error: " . $e->getMessage()); +} + +// If for some reason user is not found in DB, destroy session and redirect +if (!$user) { + session_destroy(); + header("Location: login.php"); + exit(); +} + +$projectName = 'User Dashboard'; +?> + + + + + + <?php echo htmlspecialchars($projectName); ?> + + + + + + + + + +
+
+ +
+ +
+ + +
+
+
+

Welcome to your Dashboard

+
+
+
Your Profile Information
+
+
+ + +
+
+ + +
+
+ + " readonly> +
+ +
+
+
+
+
+
+ + + + + + + diff --git a/db/migrate.php b/db/migrate.php new file mode 100644 index 0000000..9f73cf8 --- /dev/null +++ b/db/migrate.php @@ -0,0 +1,28 @@ +exec("CREATE TABLE IF NOT EXISTS migrations (migration VARCHAR(255) PRIMARY KEY)"); + + // Get executed migrations + $executed_migrations = $pdo->query("SELECT migration FROM migrations")->fetchAll(PDO::FETCH_COLUMN); + + $migration_files = glob(__DIR__ . '/migrations/*.sql'); + foreach ($migration_files as $file) { + $migration_name = basename($file); + if (!in_array($migration_name, $executed_migrations)) { + $sql = file_get_contents($file); + $pdo->exec($sql); + + $stmt = $pdo->prepare("INSERT INTO migrations (migration) VALUES (?)"); + $stmt->execute([$migration_name]); + + echo "Applied migration: " . $migration_name . "\n"; + } + } +} catch (PDOException $e) { + die("Migration failed: " . $e->getMessage()); +} \ No newline at end of file diff --git a/db/migrations/000_create_migrations_table.sql b/db/migrations/000_create_migrations_table.sql new file mode 100644 index 0000000..871a050 --- /dev/null +++ b/db/migrations/000_create_migrations_table.sql @@ -0,0 +1 @@ +CREATE TABLE IF NOT EXISTS migrations (migration VARCHAR(255) PRIMARY KEY); \ No newline at end of file diff --git a/db/migrations/001_add_status_to_users.sql b/db/migrations/001_add_status_to_users.sql new file mode 100644 index 0000000..ef89116 --- /dev/null +++ b/db/migrations/001_add_status_to_users.sql @@ -0,0 +1 @@ +ALTER TABLE users ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'active'; \ No newline at end of file diff --git a/db/migrations/002_add_status_and_description_to_cars.sql b/db/migrations/002_add_status_and_description_to_cars.sql new file mode 100644 index 0000000..c1ec660 --- /dev/null +++ b/db/migrations/002_add_status_and_description_to_cars.sql @@ -0,0 +1 @@ +ALTER TABLE cars ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'for_sale', ADD COLUMN IF NOT EXISTS description TEXT; \ No newline at end of file diff --git a/db/migrations/003_create_bookings_table.sql b/db/migrations/003_create_bookings_table.sql new file mode 100644 index 0000000..93fcb2d --- /dev/null +++ b/db/migrations/003_create_bookings_table.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS bookings ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT NOT NULL, + car_id INT NOT NULL, + booking_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + status VARCHAR(50) NOT NULL DEFAULT 'pending', -- pending, approved, canceled + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY (car_id) REFERENCES cars(id) ON DELETE CASCADE +); \ No newline at end of file diff --git a/db/migrations/004_create_reviews_table.sql b/db/migrations/004_create_reviews_table.sql new file mode 100644 index 0000000..43e8804 --- /dev/null +++ b/db/migrations/004_create_reviews_table.sql @@ -0,0 +1,11 @@ +CREATE TABLE IF NOT EXISTS reviews ( + id INT AUTO_INCREMENT PRIMARY KEY, + car_id INT NOT NULL, + user_id INT NOT NULL, + rating INT NOT NULL CHECK (rating >= 1 AND rating <= 5), + review TEXT, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + status VARCHAR(50) NOT NULL DEFAULT 'pending', -- pending, approved + FOREIGN KEY (car_id) REFERENCES cars(id) ON DELETE CASCADE, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE +); \ No newline at end of file diff --git a/db/setup_cars.php b/db/setup_cars.php new file mode 100644 index 0000000..9f62402 --- /dev/null +++ b/db/setup_cars.php @@ -0,0 +1,78 @@ +exec($sql); + echo "Table 'cars' created or already exists.
"; + + // Check if empty + $stmt = $pdo->query("SELECT COUNT(*) FROM cars"); + if ($stmt->fetchColumn() == 0) { + // Seed data + $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' => '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' => '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' => 'Toyota', + 'model' => 'Hilux', + 'year' => 2020, + 'price' => 28000.00, + 'mileage' => 15000, + '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.' + ] + ]; + + $insertSql = "INSERT INTO cars (make, model, year, price, mileage, image_url, description) VALUES (:make, :model, :year, :price, :mileage, :image_url, :description)"; + $stmt = $pdo->prepare($insertSql); + + foreach ($cars as $car) { + $stmt->execute($car); + } + echo "Seeded " . count($cars) . " cars.
"; + } else { + echo "Table 'cars' already has data.
"; + } + +} catch (PDOException $e) { + echo "Error: " . $e->getMessage(); +} diff --git a/db/setup_users.php b/db/setup_users.php new file mode 100644 index 0000000..7308721 --- /dev/null +++ b/db/setup_users.php @@ -0,0 +1,46 @@ +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'; + + $insert_sql = " + INSERT INTO users (username, email, password_hash, role) + VALUES (:username, :email, :password_hash, :role); + "; + $insert_stmt = $pdo->prepare($insert_sql); + $insert_stmt->execute([ + ':username' => $username, + ':email' => $email, + ':password_hash' => $password_hash, + ':role' => $role + ]); + echo "Default admin user created (admin@example.com / password)." . PHP_EOL; + } + + +} catch (PDOException $e) { + die("DB ERROR: " . $e->getMessage()); +} diff --git a/index.php b/index.php index 7205f3d..e6ec284 100644 --- a/index.php +++ b/index.php @@ -1,150 +1,136 @@ query("SELECT * FROM cars ORDER BY created_at DESC LIMIT 4"); + $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()); +} + +// Meta variables +$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'; ?> - + - - - New Style - - - - - - - - - - - - - - - - - - - + + + <?php echo htmlspecialchars($projectName); ?> + + + + + + + + + + + + + + + + -
-
-

Analyzing your requirements and generating your website…

-
- Loading… -
-

AI is collecting your requirements and applying the first changes.

-

This page will update automatically as the plan is implemented.

-

Runtime: PHP — UTC

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

Find Your Dream Car

+

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

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

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 new file mode 100644 index 0000000..d8d7a80 --- /dev/null +++ b/login.php @@ -0,0 +1,93 @@ +prepare("SELECT * FROM users WHERE email = :email"); + $stmt->execute(['email' => $email]); + $user = $stmt->fetch(); + + if ($user && password_verify($password, $user['password_hash'])) { + $_SESSION['user_id'] = $user['id']; + $_SESSION['username'] = $user['username']; + $_SESSION['role'] = $user['role']; + header("Location: index.php"); + exit(); + } else { + $errors[] = 'Invalid email or password'; + } + } catch (PDOException $e) { + $errors[] = "Database error: " . $e->getMessage(); + } + } +} +?> + + + + + + Login - Car Sells in Afghanistan + + + + + + + +
+
+

Login to Your Account

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

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

+
+
+ + + + + + \ No newline at end of file diff --git a/logout.php b/logout.php new file mode 100644 index 0000000..1744dc5 --- /dev/null +++ b/logout.php @@ -0,0 +1,6 @@ + +
+
+
+
Car Sells AF
+

The easiest way to buy and sell cars in Afghanistan. Secure, fast, and reliable.

+
+
+
+

© Car Sells in Afghanistan. All rights reserved.

+
+
+ \ No newline at end of file diff --git a/partials/navbar.php b/partials/navbar.php new file mode 100644 index 0000000..f8238ca --- /dev/null +++ b/partials/navbar.php @@ -0,0 +1,51 @@ + + \ No newline at end of file diff --git a/register.php b/register.php new file mode 100644 index 0000000..3976d26 --- /dev/null +++ b/register.php @@ -0,0 +1,109 @@ +prepare("SELECT COUNT(*) FROM users WHERE email = :email OR username = :username"); + $stmt->execute(['email' => $email, 'username' => $username]); + if ($stmt->fetchColumn() > 0) { + $errors[] = 'Username or email already exists'; + } else { + $password_hash = password_hash($password, PASSWORD_DEFAULT); + $insert_stmt = $pdo->prepare("INSERT INTO users (username, email, password_hash) VALUES (:username, :email, :password_hash)"); + $insert_stmt->execute([ + ':username' => $username, + ':email' => $email, + ':password_hash' => $password_hash + ]); + $_SESSION['user_id'] = $pdo->lastInsertId(); + $_SESSION['username'] = $username; + $_SESSION['role'] = 'user'; + header("Location: index.php"); + exit(); + } + } catch (PDOException $e) { + $errors[] = "Database error: " . $e->getMessage(); + } + } +} + +?> + + + + + + Register - Car Sells in Afghanistan + + + + + + + +
+
+

Create an Account

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

+ Already have an account? Login here. +

+
+
+ + + + + + \ No newline at end of file