Autosave: 20260218-083249
97
about.php
@ -1,59 +1,58 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/includes/header.php';
|
||||
// about.php
|
||||
require_once 'includes/auth.php';
|
||||
require_once 'includes/header.php';
|
||||
?>
|
||||
|
||||
<section class="container" style="padding-top: 6rem;">
|
||||
<div class="section-grid">
|
||||
<div class="container mt-5">
|
||||
<div class="grid" style="grid-template-columns: 1fr 1fr; gap: 50px; align-items: center;">
|
||||
<div>
|
||||
<div class="section-title" style="display: block; margin-bottom: 2rem;">
|
||||
<h1>About AFG_CARS</h1>
|
||||
<p style="color: var(--accent-color); font-weight: 800; text-transform: uppercase; letter-spacing: 2px;">Establishing Excellence Since 2014</p>
|
||||
</div>
|
||||
<p style="color: var(--text-secondary); margin-bottom: 2rem; font-size: 1.1rem; line-height: 2;">
|
||||
AFG_CARS is Afghanistan's premier destination for high-end automotive solutions. We specialize in sourcing the world's most desired luxury and performance vehicles, ensuring each one meets our strict criteria for quality and history.
|
||||
</p>
|
||||
<p style="color: var(--text-secondary); margin-bottom: 3rem; line-height: 1.8;">
|
||||
Our revolutionary installment programs have made luxury ownership possible for thousands of Afghan citizens, providing a transparent and secure path to vehicle ownership without the burden of immediate full-capital expenditure.
|
||||
</p>
|
||||
|
||||
<div class="grid" style="grid-template-columns: 1fr 1fr; gap: 2rem;">
|
||||
<div class="feature-box" style="padding: 1.5rem; text-align: left;">
|
||||
<h4 style="color: var(--accent-color); margin-bottom: 0.5rem;"><i class="fas fa-eye" style="font-size: 1.2rem; margin-bottom: 0;"></i> Our Vision</h4>
|
||||
<p style="font-size: 0.85rem;">To modernize the Afghan automotive market through technology and transparency.</p>
|
||||
</div>
|
||||
<div class="feature-box" style="padding: 1.5rem; text-align: left;">
|
||||
<h4 style="color: var(--accent-color); margin-bottom: 0.5rem;"><i class="fas fa-bullseye" style="font-size: 1.2rem; margin-bottom: 0;"></i> Our Mission</h4>
|
||||
<p style="font-size: 0.85rem;">Providing quality vehicles for every road and every budget with absolute integrity.</p>
|
||||
</div>
|
||||
</div>
|
||||
<h1>About AFG CARS</h1>
|
||||
<p class="mb-5" style="color: var(--primary); font-weight: bold;">Your trusted partner for premium automotive solutions in Afghanistan.</p>
|
||||
<p style="margin-bottom: 20px;">Founded in 2010, AFG CARS has established itself as the leading car dealership in Afghanistan. We specialize in importing and selling high-quality vehicles from top global manufacturers.</p>
|
||||
<p>Our mission is to provide our customers with reliable, luxury vehicles at competitive prices, backed by exceptional customer service and flexible financing options.</p>
|
||||
</div>
|
||||
|
||||
<div style="background: var(--surface-color); padding: 4rem; border-radius: var(--border-radius); border: 1px solid var(--border-color); box-shadow: 0 30px 60px rgba(0,0,0,0.5); display: flex; flex-direction: column; justify-content: center;">
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 4rem;">
|
||||
<div style="text-align: center;">
|
||||
<div style="font-size: 3.5rem; font-weight: 900; color: #fff;">2K+</div>
|
||||
<div style="color: var(--accent-color); font-size: 0.75rem; font-weight: 800; text-transform: uppercase; letter-spacing: 2px;">Cars Delivered</div>
|
||||
<div>
|
||||
<div class="card" style="padding: 40px; text-align: center; background: rgba(255,255,255,0.05);">
|
||||
<div class="grid" style="grid-template-columns: 1fr 1fr; gap: 20px;">
|
||||
<div>
|
||||
<h3 style="color: var(--primary); font-size: 2.5rem;">10+</h3>
|
||||
<p>Years Experience</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 style="color: var(--primary); font-size: 2.5rem;">5000+</h3>
|
||||
<p>Cars Sold</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 style="color: var(--primary); font-size: 2.5rem;">4</h3>
|
||||
<p>Major Branches</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 style="color: var(--primary); font-size: 2.5rem;">100%</h3>
|
||||
<p>Satisfaction</p>
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
<div style="font-size: 3.5rem; font-weight: 900; color: #fff;">04</div>
|
||||
<div style="color: var(--accent-color); font-size: 0.75rem; font-weight: 800; text-transform: uppercase; letter-spacing: 2px;">Showrooms</div>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
<div style="font-size: 3.5rem; font-weight: 900; color: #fff;">15+</div>
|
||||
<div style="color: var(--accent-color); font-size: 0.75rem; font-weight: 800; text-transform: uppercase; letter-spacing: 2px;">Global Partners</div>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
<div style="font-size: 3.5rem; font-weight: 900; color: #fff;">100%</div>
|
||||
<div style="color: var(--accent-color); font-size: 0.75rem; font-weight: 800; text-transform: uppercase; letter-spacing: 2px;">Verified Stock</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 4rem; text-align: center; border-top: 1px solid rgba(255,255,255,0.05); padding-top: 2rem;">
|
||||
<p style="font-size: 0.9rem; color: var(--text-secondary);">Enterprise System Managed by <strong>Mohammad Sadiq</strong></p>
|
||||
<p style="font-size: 0.7rem; color: var(--text-secondary); opacity: 0.4; margin-top: 0.5rem;">Lamp Stack | MariaDB | PHP 8.2 | Premium UI</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="mt-5">
|
||||
<h2 class="text-center mb-5">Our Core Values</h2>
|
||||
<div class="grid" style="grid-template-columns: repeat(3, 1fr);">
|
||||
<div class="card" style="padding: 20px; text-align: center;">
|
||||
<h3 style="color: var(--primary);">Integrity</h3>
|
||||
<p>We believe in transparent pricing and honest dealings with every customer.</p>
|
||||
</div>
|
||||
<div class="card" style="padding: 20px; text-align: center;">
|
||||
<h3 style="color: var(--primary);">Quality</h3>
|
||||
<p>Every vehicle undergoes a rigorous 150-point inspection before sale.</p>
|
||||
</div>
|
||||
<div class="card" style="padding: 20px; text-align: center;">
|
||||
<h3 style="color: var(--primary);">Service</h3>
|
||||
<p>Our relationship doesn't end at the sale; we provide ongoing support.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
<?php require_once 'includes/footer.php'; ?>
|
||||
|
||||
256
admin/cars.php
@ -1,188 +1,106 @@
|
||||
<?php
|
||||
require_once 'includes/header.php';
|
||||
// admin/cars.php
|
||||
require_once '../includes/auth.php';
|
||||
require_once '../includes/middleware.php';
|
||||
requireAdmin();
|
||||
require_once '../includes/header.php';
|
||||
global $pdo;
|
||||
|
||||
$pdo = db();
|
||||
$error = '';
|
||||
$success = '';
|
||||
$msg = '';
|
||||
|
||||
// Handle Delete
|
||||
if (isset($_POST['delete_car'])) {
|
||||
$id = $_POST['car_id'];
|
||||
if (isset($_GET['delete'])) {
|
||||
$stmt = $pdo->prepare("DELETE FROM cars WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$success = "Car deleted successfully.";
|
||||
$stmt->execute([$_GET['delete']]);
|
||||
$msg = "Car deleted successfully.";
|
||||
}
|
||||
|
||||
// Handle Add (Basic Implementation)
|
||||
if (isset($_POST['add_car'])) {
|
||||
try {
|
||||
$image_url = 'assets/images/cars/default.jpg'; // Default
|
||||
|
||||
// Handle Image Upload
|
||||
if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) {
|
||||
$uploadDir = __DIR__ . '/../assets/images/cars/';
|
||||
if (!is_dir($uploadDir)) mkdir($uploadDir, 0777, true);
|
||||
|
||||
$ext = pathinfo($_FILES['image']['name'], PATHINFO_EXTENSION);
|
||||
$filename = uniqid('car_') . '.' . $ext;
|
||||
$targetPath = $uploadDir . $filename;
|
||||
|
||||
if (move_uploaded_file($_FILES['image']['tmp_name'], $targetPath)) {
|
||||
$image_url = 'assets/images/cars/' . $filename;
|
||||
}
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO cars (vin, brand, model, year, price, mileage, transmission, fuel_type, status, branch_id, dealer_id, installment_available, image_url) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'Available', ?, ?, ?, ?)");
|
||||
$stmt->execute([
|
||||
$_POST['vin'], $_POST['brand'], $_POST['model'], $_POST['year'],
|
||||
$_POST['price'], $_POST['mileage'], $_POST['transmission'],
|
||||
$_POST['fuel_type'], $_POST['branch_id'], $_POST['dealer_id'] ?: null,
|
||||
isset($_POST['installment_available']) ? 1 : 0,
|
||||
$image_url
|
||||
]);
|
||||
|
||||
// Log activity
|
||||
$adminId = $_SESSION['user_id'];
|
||||
$pdo->prepare("INSERT INTO activity_logs (user_id, action) VALUES (?, 'Added new car: ' . ?)")->execute([$adminId, $_POST['brand'] . ' ' . $_POST['model']]);
|
||||
|
||||
$success = "Car added successfully.";
|
||||
} catch (PDOException $e) {
|
||||
$error = "Error adding car: " . $e->getMessage();
|
||||
}
|
||||
// Handle Add
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$brand = $_POST['brand'];
|
||||
$model = $_POST['model'];
|
||||
$year = $_POST['year'];
|
||||
$price = $_POST['price'];
|
||||
$branch_id = $_POST['branch_id'];
|
||||
|
||||
// Use a random placeholder from the 20 generated ones for demo purposes
|
||||
$random_img = rand(1, 20);
|
||||
$image_path = "assets/images/cars/car{$random_img}.jpg";
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO cars (brand, model, year, price, branch_id, status, image_path, is_featured) VALUES (?, ?, ?, ?, ?, 'available', ?, 0)");
|
||||
$stmt->execute([$brand, $model, $year, $price, $branch_id, $image_path]);
|
||||
$msg = "Car added successfully.";
|
||||
}
|
||||
|
||||
// Fetch Cars
|
||||
$cars = $pdo->query("SELECT cars.*, branches.city FROM cars LEFT JOIN branches ON cars.branch_id = branches.id ORDER BY created_at DESC")->fetchAll();
|
||||
$branches = $pdo->query("SELECT * FROM branches")->fetchAll();
|
||||
$dealers = $pdo->query("SELECT * FROM users WHERE role = 'Dealer'")->fetchAll();
|
||||
|
||||
$stmt = $pdo->query("SELECT cars.*, branches.name as branch_name FROM cars LEFT JOIN branches ON cars.branch_id = branches.id ORDER BY cars.created_at DESC");
|
||||
$cars = $stmt->fetchAll();
|
||||
?>
|
||||
|
||||
<div class="page-header">
|
||||
<h1>Car Management</h1>
|
||||
<button onclick="document.getElementById('addCarModal').style.display='block'" class="btn-sm btn-primary" style="background:var(--accent-color); color:var(--bg-color); border:none; padding:0.5rem 1rem; cursor:pointer;">Add New Car</button>
|
||||
</div>
|
||||
<div class="dashboard-container">
|
||||
<div class="sidebar">
|
||||
<h3 class="mb-5" style="color: var(--primary);">Admin Panel</h3>
|
||||
<a href="index.php">Dashboard</a>
|
||||
<a href="cars.php" class="active">Manage Cars</a>
|
||||
<a href="users.php">Manage Users</a>
|
||||
<a href="branches.php">Manage Branches</a>
|
||||
<a href="sales.php">Sales & Installments</a>
|
||||
<a href="../logout.php" class="mt-5" style="color: #e63946;">Logout</a>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<h1>Manage Cars</h1>
|
||||
|
||||
<?php if ($msg): ?>
|
||||
<div class="alert alert-success" style="background: rgba(42, 157, 143, 0.2); color: #2a9d8f; padding: 10px; margin-bottom: 20px;">
|
||||
<?= htmlspecialchars($msg) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($error): ?><div style="color: #ff4444; margin-bottom: 1rem;"><?php echo htmlspecialchars($error); ?></div><?php endif; ?>
|
||||
<?php if ($success): ?><div style="color: #00C851; margin-bottom: 1rem;"><?php echo htmlspecialchars($success); ?></div><?php endif; ?>
|
||||
|
||||
<div style="overflow-x: auto;">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Brand/Model</th>
|
||||
<th>Year</th>
|
||||
<th>Price</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($cars as $car): ?>
|
||||
<tr>
|
||||
<td>#<?php echo $car['id']; ?></td>
|
||||
<td><?php echo htmlspecialchars($car['brand'] . ' ' . $car['model']); ?></td>
|
||||
<td><?php echo $car['year']; ?></td>
|
||||
<td>$<?php echo number_format($car['price']); ?></td>
|
||||
<td><?php echo $car['status']; ?></td>
|
||||
<td>
|
||||
<form method="POST" onsubmit="return confirm('Delete this car?');" style="display: inline;">
|
||||
<input type="hidden" name="car_id" value="<?php echo $car['id']; ?>">
|
||||
<button type="submit" name="delete_car" class="btn-sm btn-danger">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Simple Add Car Modal -->
|
||||
<div id="addCarModal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.8); z-index:2000; align-items:center; justify-content:center; display:none;">
|
||||
<div style="background:var(--card-bg); margin:5% auto; padding:2rem; width:90%; max-width:500px; border-radius:12px; border:1px solid var(--border-color); position: relative; max-height: 90vh; overflow-y: auto;">
|
||||
<h2 style="margin-bottom:1.5rem; text-align:center;">Add New Car</h2>
|
||||
<form method="POST" enctype="multipart/form-data">
|
||||
<div style="margin-bottom:1rem;">
|
||||
<label style="display:block; margin-bottom:0.5rem;">Vehicle Image</label>
|
||||
<input type="file" name="image" accept="image/*" required style="width:100%; padding:0.8rem; background:var(--bg-color); border:1px solid var(--border-color); color:white; border-radius:4px;">
|
||||
</div>
|
||||
<div style="margin-bottom:1rem;">
|
||||
<input type="text" name="vin" placeholder="VIN Number" required style="width:100%; padding:0.8rem; background:var(--bg-color); border:1px solid var(--border-color); color:white; border-radius:4px;">
|
||||
</div>
|
||||
<div style="display:grid; grid-template-columns: 1fr 1fr; gap:1rem; margin-bottom:1rem;">
|
||||
<input type="text" name="brand" placeholder="Brand" required style="width:100%; padding:0.8rem; background:var(--bg-color); border:1px solid var(--border-color); color:white; border-radius:4px;">
|
||||
<input type="text" name="model" placeholder="Model" required style="width:100%; padding:0.8rem; background:var(--bg-color); border:1px solid var(--border-color); color:white; border-radius:4px;">
|
||||
</div>
|
||||
<div style="display:grid; grid-template-columns: 1fr 1fr; gap:1rem; margin-bottom:1rem;">
|
||||
<input type="number" name="year" placeholder="Year" required style="width:100%; padding:0.8rem; background:var(--bg-color); border:1px solid var(--border-color); color:white; border-radius:4px;">
|
||||
<input type="number" name="price" placeholder="Price ($)" required style="width:100%; padding:0.8rem; background:var(--bg-color); border:1px solid var(--border-color); color:white; border-radius:4px;">
|
||||
</div>
|
||||
<div style="margin-bottom:1rem;">
|
||||
<input type="number" name="mileage" placeholder="Mileage" required style="width:100%; padding:0.8rem; background:var(--bg-color); border:1px solid var(--border-color); color:white; border-radius:4px;">
|
||||
</div>
|
||||
<div style="display:grid; grid-template-columns: 1fr 1fr; gap:1rem; margin-bottom:1.5rem;">
|
||||
<select name="transmission" style="width:100%; padding:0.8rem; background:var(--bg-color); border:1px solid var(--border-color); color:white; border-radius:4px;">
|
||||
<option value="Automatic">Automatic</option>
|
||||
<option value="Manual">Manual</option>
|
||||
</select>
|
||||
<select name="fuel_type" style="width:100%; padding:0.8rem; background:var(--bg-color); border:1px solid var(--border-color); color:white; border-radius:4px;">
|
||||
<option value="Petrol">Petrol</option>
|
||||
<option value="Diesel">Diesel</option>
|
||||
<option value="Electric">Electric</option>
|
||||
<option value="Hybrid">Hybrid</option>
|
||||
</select>
|
||||
</div>
|
||||
<div style="display:grid; grid-template-columns: 1fr 1fr; gap:1rem; margin-bottom:1rem;">
|
||||
<select name="branch_id" required style="width:100%; padding:0.8rem; background:var(--bg-color); border:1px solid var(--border-color); color:white; border-radius:4px;">
|
||||
<!-- Add Car Form -->
|
||||
<div class="card mb-5" style="padding: 20px;">
|
||||
<h3>Add New Car</h3>
|
||||
<form method="POST" class="grid" style="grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px;">
|
||||
<input type="text" name="brand" placeholder="Brand" class="form-control" required>
|
||||
<input type="text" name="model" placeholder="Model" class="form-control" required>
|
||||
<input type="number" name="year" placeholder="Year" class="form-control" required>
|
||||
<input type="number" name="price" placeholder="Price" class="form-control" required>
|
||||
<select name="branch_id" class="form-control" required>
|
||||
<option value="">Select Branch</option>
|
||||
<?php foreach ($branches as $branch): ?>
|
||||
<option value="<?php echo $branch['id']; ?>"><?php echo htmlspecialchars($branch['name']); ?></option>
|
||||
<?php foreach ($branches as $b): ?>
|
||||
<option value="<?= $b['id'] ?>"><?= htmlspecialchars($b['city']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<select name="dealer_id" style="width:100%; padding:0.8rem; background:var(--bg-color); border:1px solid var(--border-color); color:white; border-radius:4px;">
|
||||
<option value="">Select Dealer (Optional)</option>
|
||||
<?php foreach ($dealers as $dealer): ?>
|
||||
<option value="<?php echo $dealer['id']; ?>"><?php echo htmlspecialchars($dealer['username']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div style="margin-bottom:1.5rem;">
|
||||
<label style="color:var(--text-secondary); display:flex; align-items:center;">
|
||||
<input type="checkbox" name="installment_available" value="1" style="width:auto; margin-right:0.5rem;">
|
||||
Available for Installment Plan
|
||||
</label>
|
||||
</div>
|
||||
<button type="submit" name="add_car" style="width:100%; padding:0.8rem; background:var(--accent-color); border:none; border-radius:4px; color:var(--bg-color); font-weight:bold; cursor:pointer;">Add Car</button>
|
||||
<button type="button" onclick="document.getElementById('addCarModal').style.display='none'" style="width:100%; padding:0.8rem; margin-top:0.5rem; background:transparent; border:1px solid var(--border-color); color:var(--text-secondary); cursor:pointer; border-radius:4px;">Cancel</button>
|
||||
</form>
|
||||
<button type="submit" class="btn">Add Car</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card" style="padding: 20px;">
|
||||
<h3>Inventory List</h3>
|
||||
<table>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Car</th>
|
||||
<th>Price</th>
|
||||
<th>Branch</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
<?php foreach ($cars as $car): ?>
|
||||
<tr>
|
||||
<td><?= $car['id'] ?></td>
|
||||
<td><?= htmlspecialchars($car['year'] . ' ' . $car['brand'] . ' ' . $car['model']) ?></td>
|
||||
<td>$<?= number_format((float)$car['price']) ?></td>
|
||||
<td><?= htmlspecialchars($car['city'] ?? 'N/A') ?></td>
|
||||
<td><?= htmlspecialchars(ucfirst($car['status'])) ?></td>
|
||||
<td>
|
||||
<a href="?delete=<?= $car['id'] ?>" style="color: #e63946;" onclick="return confirm('Are you sure?')">Delete</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Simple script to handle modal visibility since inline style display:none might be overridden by flex in CSS if I used flex
|
||||
// Actually the inline style 'display:none' on the wrapper div is correct.
|
||||
// I'll add a script to ensure it works.
|
||||
const modal = document.getElementById('addCarModal');
|
||||
|
||||
// Auto-open if action=add
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (urlParams.get('action') === 'add') {
|
||||
modal.style.display = 'flex';
|
||||
} else {
|
||||
// Ensure it's hidden on load if not requested
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
|
||||
// Override the button to show it as flex for centering
|
||||
document.querySelector('.page-header button').onclick = function() {
|
||||
modal.style.display = 'flex';
|
||||
};
|
||||
|
||||
// Close on cancel
|
||||
document.querySelector('#addCarModal button[type="button"]').onclick = function() {
|
||||
modal.style.display = 'none';
|
||||
};
|
||||
</script>
|
||||
|
||||
<?php require_once 'includes/footer.php'; ?>
|
||||
<?php require_once '../includes/footer.php'; ?>
|
||||
|
||||
78
admin/dashboard.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
// admin/index.php
|
||||
require_once '../includes/auth.php';
|
||||
require_once '../includes/middleware.php';
|
||||
requireAdmin();
|
||||
require_once '../includes/header.php';
|
||||
global $pdo;
|
||||
|
||||
// Fetch Stats
|
||||
$total_cars = $pdo->query("SELECT COUNT(*) FROM cars")->fetchColumn();
|
||||
$total_users = $pdo->query("SELECT COUNT(*) FROM users")->fetchColumn();
|
||||
$total_sales = $pdo->query("SELECT COUNT(*) FROM sales")->fetchColumn();
|
||||
$pending_inquiries = $pdo->query("SELECT COUNT(*) FROM inquiries")->fetchColumn();
|
||||
?>
|
||||
|
||||
<div class="dashboard-container">
|
||||
<div class="sidebar">
|
||||
<h3 class="mb-5" style="color: var(--primary);">Admin Panel</h3>
|
||||
<a href="index.php" class="active">Dashboard</a>
|
||||
<a href="cars.php">Manage Cars</a>
|
||||
<a href="users.php">Manage Users</a>
|
||||
<a href="branches.php">Manage Branches</a>
|
||||
<a href="sales.php">Sales & Installments</a>
|
||||
<a href="../logout.php" class="mt-5" style="color: #e63946;">Logout</a>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<h1>Dashboard</h1>
|
||||
<p class="mb-5">Welcome back, <?= htmlspecialchars(getUserName()) ?></p>
|
||||
|
||||
<div class="grid" style="grid-template-columns: repeat(4, 1fr); gap: 20px; margin-bottom: 40px;">
|
||||
<div class="card" style="padding: 20px; text-align: center;">
|
||||
<h3>Total Cars</h3>
|
||||
<div class="stat-number" style="font-size: 2rem; color: var(--primary); font-weight: bold;"><?= $total_cars ?></div>
|
||||
</div>
|
||||
<div class="card" style="padding: 20px; text-align: center;">
|
||||
<h3>Total Users</h3>
|
||||
<div class="stat-number" style="font-size: 2rem; color: var(--primary); font-weight: bold;"><?= $total_users ?></div>
|
||||
</div>
|
||||
<div class="card" style="padding: 20px; text-align: center;">
|
||||
<h3>Sales</h3>
|
||||
<div class="stat-number" style="font-size: 2rem; color: var(--primary); font-weight: bold;"><?= $total_sales ?></div>
|
||||
</div>
|
||||
<div class="card" style="padding: 20px; text-align: center;">
|
||||
<h3>Inquiries</h3>
|
||||
<div class="stat-number" style="font-size: 2rem; color: var(--primary); font-weight: bold;"><?= $pending_inquiries ?></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" style="padding: 20px;">
|
||||
<h3>Recent Inquiries</h3>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Message</th>
|
||||
</tr>
|
||||
<?php
|
||||
try {
|
||||
$stmt = $pdo->query("SELECT * FROM inquiries ORDER BY created_at DESC LIMIT 5");
|
||||
while ($row = $stmt->fetch()):
|
||||
?>
|
||||
<tr>
|
||||
<td><?= date('M d, Y', strtotime($row['created_at'])) ?></td>
|
||||
<td><?= htmlspecialchars($row['name']) ?></td>
|
||||
<td><?= htmlspecialchars($row['email']) ?></td>
|
||||
<td><?= htmlspecialchars(substr($row['message'], 0, 50)) ?>...</td>
|
||||
</tr>
|
||||
<?php endwhile;
|
||||
} catch(Exception $e) { echo "<tr><td colspan='4'>No data</td></tr>"; }
|
||||
?>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once '../includes/footer.php'; ?>
|
||||
0
admin/employees.php
Normal file
@ -108,9 +108,9 @@ requireAdmin();
|
||||
<body>
|
||||
<div class="admin-layout">
|
||||
<aside class="sidebar">
|
||||
<a href="/index.php" class="sidebar-brand">AFG_CARS ADMIN</a>
|
||||
<a href="/admin/dashboard.php" class="sidebar-brand">AFG_CARS ADMIN</a>
|
||||
<nav>
|
||||
<a href="/admin/index.php" class="nav-link <?php echo basename($_SERVER['PHP_SELF']) == 'index.php' ? 'active' : ''; ?>">Dashboard</a>
|
||||
<a href="/admin/dashboard.php" class="nav-link <?php echo basename($_SERVER['PHP_SELF']) == 'dashboard.php' ? 'active' : ''; ?>">Dashboard</a>
|
||||
<a href="/admin/users.php" class="nav-link <?php echo basename($_SERVER['PHP_SELF']) == 'users.php' ? 'active' : ''; ?>">Users</a>
|
||||
<a href="/admin/branches.php" class="nav-link <?php echo basename($_SERVER['PHP_SELF']) == 'branches.php' ? 'active' : ''; ?>">Branches</a>
|
||||
<a href="/admin/dealers.php" class="nav-link <?php echo basename($_SERVER['PHP_SELF']) == 'dealers.php' ? 'active' : ''; ?>">Dealers</a>
|
||||
|
||||
@ -1,80 +1 @@
|
||||
<?php
|
||||
require_once 'includes/header.php';
|
||||
|
||||
$pdo = db();
|
||||
$usersCount = $pdo->query("SELECT COUNT(*) FROM users")->fetchColumn();
|
||||
$carsCount = $pdo->query("SELECT COUNT(*) FROM cars")->fetchColumn();
|
||||
$branchesCount = $pdo->query("SELECT COUNT(*) FROM branches")->fetchColumn();
|
||||
$salesCount = $pdo->query("SELECT COUNT(*) FROM sales WHERE status = 'Completed'")->fetchColumn();
|
||||
$revenue = $pdo->query("SELECT SUM(final_price) FROM sales WHERE status = 'Completed'")->fetchColumn() ?: 0;
|
||||
$activeContracts = $pdo->query("SELECT COUNT(*) FROM installments WHERE status = 'Active'")->fetchColumn();
|
||||
|
||||
// Recent Activity
|
||||
$activities = $pdo->query("SELECT * FROM activity_logs ORDER BY created_at DESC LIMIT 5")->fetchAll();
|
||||
?>
|
||||
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h1>Enterprise Dashboard</h1>
|
||||
<span style="color: var(--text-secondary);">Overview of AFG_CARS Operations</span>
|
||||
</div>
|
||||
<div style="background: rgba(255,255,255,0.1); padding: 0.5rem 1rem; border-radius: 4px;">
|
||||
Date: <?php echo date('Y-m-d'); ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value"><?php echo $usersCount; ?></div>
|
||||
<div class="stat-label">Total Users</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value"><?php echo $branchesCount; ?></div>
|
||||
<div class="stat-label">Branches</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value"><?php echo $carsCount; ?></div>
|
||||
<div class="stat-label">Total Inventory</div>
|
||||
</div>
|
||||
<div class="stat-card" style="border-color: var(--accent-color);">
|
||||
<div class="stat-value">$<?php echo number_format($revenue); ?></div>
|
||||
<div class="stat-label">Total Revenue</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value"><?php echo $salesCount; ?></div>
|
||||
<div class="stat-label">Completed Sales</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value"><?php echo $activeContracts; ?></div>
|
||||
<div class="stat-label">Active Installments</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 2fr 1fr; gap: 1.5rem;">
|
||||
<div style="background: var(--card-bg); border-radius: var(--border-radius); padding: 1.5rem; border: 1px solid var(--border-color);">
|
||||
<h3 style="margin-bottom: 1rem; color: var(--accent-color);">System Activity Log</h3>
|
||||
<?php if ($activities): ?>
|
||||
<ul style="list-style: none; padding: 0;">
|
||||
<?php foreach ($activities as $log): ?>
|
||||
<li style="border-bottom: 1px solid var(--border-color); padding: 0.8rem 0; display: flex; justify-content: space-between;">
|
||||
<span><?php echo htmlspecialchars($log['action']); ?></span>
|
||||
<small style="color: var(--text-secondary);"><?php echo $log['created_at']; ?></small>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php else: ?>
|
||||
<p style="color: var(--text-secondary);">No recent activity logged.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div style="background: var(--card-bg); border-radius: var(--border-radius); padding: 1.5rem; border: 1px solid var(--border-color);">
|
||||
<h3 style="margin-bottom: 1rem; color: var(--accent-color);">Quick Actions</h3>
|
||||
<div style="display: flex; flex-direction: column; gap: 0.8rem;">
|
||||
<a href="cars.php?action=add" class="btn-sm btn-primary" style="text-align: center;">Add New Car</a>
|
||||
<a href="branches.php?action=add" class="btn-sm" style="background: #444; color: white; text-align: center;">New Branch</a>
|
||||
<a href="users.php?action=add" class="btn-sm" style="background: #444; color: white; text-align: center;">Add User</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once 'includes/footer.php'; ?>
|
||||
<?php header('Location: dashboard.php'); ?>
|
||||
|
||||
0
admin/installments.php
Normal file
0
admin/settings.php
Normal file
@ -1,493 +0,0 @@
|
||||
<?php
|
||||
// LocalAIApi — proxy client for the Responses API.
|
||||
// Usage (async: auto-polls status until ready):
|
||||
// require_once __DIR__ . '/ai/LocalAIApi.php';
|
||||
// $response = LocalAIApi::createResponse([
|
||||
// 'input' => [
|
||||
// ['role' => 'system', 'content' => 'You are a helpful assistant.'],
|
||||
// ['role' => 'user', 'content' => 'Tell me a bedtime story.'],
|
||||
// ],
|
||||
// ]);
|
||||
// if (!empty($response['success'])) {
|
||||
// // response['data'] contains full payload, e.g.:
|
||||
// // {
|
||||
// // "id": "resp_xxx",
|
||||
// // "status": "completed",
|
||||
// // "output": [
|
||||
// // {"type": "reasoning", "summary": []},
|
||||
// // {"type": "message", "content": [{"type": "output_text", "text": "Your final answer here."}]}
|
||||
// // ]
|
||||
// // }
|
||||
// $decoded = LocalAIApi::decodeJsonFromResponse($response); // or inspect $response['data'] / extractText(...)
|
||||
// }
|
||||
// Poll settings override:
|
||||
// LocalAIApi::createResponse($payload, ['poll_interval' => 5, 'poll_timeout' => 300]);
|
||||
|
||||
class LocalAIApi
|
||||
{
|
||||
/** @var array<string,mixed>|null */
|
||||
private static ?array $configCache = null;
|
||||
|
||||
/**
|
||||
* Signature compatible with the OpenAI Responses API.
|
||||
*
|
||||
* @param array<string,mixed> $params Request body (model, input, text, reasoning, metadata, etc.).
|
||||
* @param array<string,mixed> $options Extra options (timeout, verify_tls, headers, path, project_uuid).
|
||||
* @return array{
|
||||
* success:bool,
|
||||
* status?:int,
|
||||
* data?:mixed,
|
||||
* error?:string,
|
||||
* response?:mixed,
|
||||
* message?:string
|
||||
* }
|
||||
*/
|
||||
public static function createResponse(array $params, array $options = []): array
|
||||
{
|
||||
$cfg = self::config();
|
||||
$payload = $params;
|
||||
|
||||
if (empty($payload['input']) || !is_array($payload['input'])) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'input_missing',
|
||||
'message' => 'Parameter "input" is required and must be an array.',
|
||||
];
|
||||
}
|
||||
|
||||
if (!isset($payload['model']) || $payload['model'] === '') {
|
||||
$payload['model'] = $cfg['default_model'];
|
||||
}
|
||||
|
||||
$initial = self::request($options['path'] ?? null, $payload, $options);
|
||||
if (empty($initial['success'])) {
|
||||
return $initial;
|
||||
}
|
||||
|
||||
// Async flow: if backend returns ai_request_id, poll status until ready
|
||||
$data = $initial['data'] ?? null;
|
||||
if (is_array($data) && isset($data['ai_request_id'])) {
|
||||
$aiRequestId = $data['ai_request_id'];
|
||||
$pollTimeout = isset($options['poll_timeout']) ? (int) $options['poll_timeout'] : 300; // seconds
|
||||
$pollInterval = isset($options['poll_interval']) ? (int) $options['poll_interval'] : 5; // seconds
|
||||
return self::awaitResponse($aiRequestId, [
|
||||
'timeout' => $pollTimeout,
|
||||
'interval' => $pollInterval,
|
||||
'headers' => $options['headers'] ?? [],
|
||||
'timeout_per_call' => $options['timeout'] ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
return $initial;
|
||||
}
|
||||
|
||||
/**
|
||||
* Snake_case alias for createResponse (matches the provided example).
|
||||
*
|
||||
* @param array<string,mixed> $params
|
||||
* @param array<string,mixed> $options
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public static function create_response(array $params, array $options = []): array
|
||||
{
|
||||
return self::createResponse($params, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a raw request to the AI proxy.
|
||||
*
|
||||
* @param string $path Endpoint (may be an absolute URL).
|
||||
* @param array<string,mixed> $payload JSON payload.
|
||||
* @param array<string,mixed> $options Additional request options.
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public static function request(?string $path = null, array $payload = [], array $options = []): array
|
||||
{
|
||||
$cfg = self::config();
|
||||
|
||||
$projectUuid = $cfg['project_uuid'];
|
||||
if (empty($projectUuid)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'project_uuid_missing',
|
||||
'message' => 'PROJECT_UUID is not defined; aborting AI request.',
|
||||
];
|
||||
}
|
||||
|
||||
$defaultPath = $cfg['responses_path'] ?? null;
|
||||
$resolvedPath = $path ?? ($options['path'] ?? $defaultPath);
|
||||
if (empty($resolvedPath)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'project_id_missing',
|
||||
'message' => 'PROJECT_ID is not defined; cannot resolve AI proxy endpoint.',
|
||||
];
|
||||
}
|
||||
|
||||
$url = self::buildUrl($resolvedPath, $cfg['base_url']);
|
||||
$baseTimeout = isset($cfg['timeout']) ? (int) $cfg['timeout'] : 30;
|
||||
$timeout = isset($options['timeout']) ? (int) $options['timeout'] : $baseTimeout;
|
||||
if ($timeout <= 0) {
|
||||
$timeout = 30;
|
||||
}
|
||||
|
||||
$baseVerifyTls = array_key_exists('verify_tls', $cfg) ? (bool) $cfg['verify_tls'] : true;
|
||||
$verifyTls = array_key_exists('verify_tls', $options)
|
||||
? (bool) $options['verify_tls']
|
||||
: $baseVerifyTls;
|
||||
|
||||
$projectHeader = $cfg['project_header'];
|
||||
|
||||
$headers = [
|
||||
'Content-Type: application/json',
|
||||
'Accept: application/json',
|
||||
];
|
||||
$headers[] = $projectHeader . ': ' . $projectUuid;
|
||||
if (!empty($options['headers']) && is_array($options['headers'])) {
|
||||
foreach ($options['headers'] as $header) {
|
||||
if (is_string($header) && $header !== '') {
|
||||
$headers[] = $header;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($projectUuid) && !array_key_exists('project_uuid', $payload)) {
|
||||
$payload['project_uuid'] = $projectUuid;
|
||||
}
|
||||
|
||||
$body = json_encode($payload, JSON_UNESCAPED_UNICODE);
|
||||
if ($body === false) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'json_encode_failed',
|
||||
'message' => 'Failed to encode request body to JSON.',
|
||||
];
|
||||
}
|
||||
|
||||
return self::sendCurl($url, 'POST', $body, $headers, $timeout, $verifyTls);
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll AI request status until ready or timeout.
|
||||
*
|
||||
* @param int|string $aiRequestId
|
||||
* @param array<string,mixed> $options
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public static function awaitResponse($aiRequestId, array $options = []): array
|
||||
{
|
||||
$cfg = self::config();
|
||||
|
||||
$timeout = isset($options['timeout']) ? (int) $options['timeout'] : 300; // seconds
|
||||
$interval = isset($options['interval']) ? (int) $options['interval'] : 5; // seconds
|
||||
if ($interval <= 0) {
|
||||
$interval = 5;
|
||||
}
|
||||
$perCallTimeout = isset($options['timeout_per_call']) ? (int) $options['timeout_per_call'] : null;
|
||||
|
||||
$deadline = time() + max($timeout, $interval);
|
||||
$headers = $options['headers'] ?? [];
|
||||
|
||||
while (true) {
|
||||
$statusResp = self::fetchStatus($aiRequestId, [
|
||||
'headers' => $headers,
|
||||
'timeout' => $perCallTimeout,
|
||||
]);
|
||||
if (!empty($statusResp['success'])) {
|
||||
$data = $statusResp['data'] ?? [];
|
||||
if (is_array($data)) {
|
||||
$statusValue = $data['status'] ?? null;
|
||||
if ($statusValue === 'success') {
|
||||
return [
|
||||
'success' => true,
|
||||
'status' => 200,
|
||||
'data' => $data['response'] ?? $data,
|
||||
];
|
||||
}
|
||||
if ($statusValue === 'failed') {
|
||||
return [
|
||||
'success' => false,
|
||||
'status' => 500,
|
||||
'error' => isset($data['error']) ? (string)$data['error'] : 'AI request failed',
|
||||
'data' => $data,
|
||||
];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return $statusResp;
|
||||
}
|
||||
|
||||
if (time() >= $deadline) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'timeout',
|
||||
'message' => 'Timed out waiting for AI response.',
|
||||
];
|
||||
}
|
||||
sleep($interval);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch status for queued AI request.
|
||||
*
|
||||
* @param int|string $aiRequestId
|
||||
* @param array<string,mixed> $options
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public static function fetchStatus($aiRequestId, array $options = []): array
|
||||
{
|
||||
$cfg = self::config();
|
||||
$projectUuid = $cfg['project_uuid'];
|
||||
if (empty($projectUuid)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'project_uuid_missing',
|
||||
'message' => 'PROJECT_UUID is not defined; aborting status check.',
|
||||
];
|
||||
}
|
||||
|
||||
$statusPath = self::resolveStatusPath($aiRequestId, $cfg);
|
||||
$url = self::buildUrl($statusPath, $cfg['base_url']);
|
||||
|
||||
$baseTimeout = isset($cfg['timeout']) ? (int) $cfg['timeout'] : 30;
|
||||
$timeout = isset($options['timeout']) ? (int) $options['timeout'] : $baseTimeout;
|
||||
if ($timeout <= 0) {
|
||||
$timeout = 30;
|
||||
}
|
||||
|
||||
$baseVerifyTls = array_key_exists('verify_tls', $cfg) ? (bool) $cfg['verify_tls'] : true;
|
||||
$verifyTls = array_key_exists('verify_tls', $options)
|
||||
? (bool) $options['verify_tls']
|
||||
: $baseVerifyTls;
|
||||
|
||||
$projectHeader = $cfg['project_header'];
|
||||
$headers = [
|
||||
'Accept: application/json',
|
||||
$projectHeader . ': ' . $projectUuid,
|
||||
];
|
||||
if (!empty($options['headers']) && is_array($options['headers'])) {
|
||||
foreach ($options['headers'] as $header) {
|
||||
if (is_string($header) && $header !== '') {
|
||||
$headers[] = $header;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self::sendCurl($url, 'GET', null, $headers, $timeout, $verifyTls);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract plain text from a Responses API payload.
|
||||
*
|
||||
* @param array<string,mixed> $response Result of LocalAIApi::createResponse|request.
|
||||
* @return string
|
||||
*/
|
||||
public static function extractText(array $response): string
|
||||
{
|
||||
$payload = $response['data'] ?? $response;
|
||||
if (!is_array($payload)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!empty($payload['output']) && is_array($payload['output'])) {
|
||||
$combined = '';
|
||||
foreach ($payload['output'] as $item) {
|
||||
if (!isset($item['content']) || !is_array($item['content'])) {
|
||||
continue;
|
||||
}
|
||||
foreach ($item['content'] as $block) {
|
||||
if (is_array($block) && ($block['type'] ?? '') === 'output_text' && !empty($block['text'])) {
|
||||
$combined .= $block['text'];
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($combined !== '') {
|
||||
return $combined;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($payload['choices'][0]['message']['content'])) {
|
||||
return (string) $payload['choices'][0]['message']['content'];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to decode JSON emitted by the model (handles markdown fences).
|
||||
*
|
||||
* @param array<string,mixed> $response
|
||||
* @return array<string,mixed>|null
|
||||
*/
|
||||
public static function decodeJsonFromResponse(array $response): ?array
|
||||
{
|
||||
$text = self::extractText($response);
|
||||
if ($text === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$decoded = json_decode($text, true);
|
||||
if (is_array($decoded)) {
|
||||
return $decoded;
|
||||
}
|
||||
|
||||
$stripped = preg_replace('/^```json|```$/m', '', trim($text));
|
||||
if ($stripped !== null && $stripped !== $text) {
|
||||
$decoded = json_decode($stripped, true);
|
||||
if (is_array($decoded)) {
|
||||
return $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load configuration from ai/config.php.
|
||||
*
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
private static function config(): array
|
||||
{
|
||||
if (self::$configCache === null) {
|
||||
$configPath = __DIR__ . '/config.php';
|
||||
if (!file_exists($configPath)) {
|
||||
throw new RuntimeException('AI config file not found: ai/config.php');
|
||||
}
|
||||
$cfg = require $configPath;
|
||||
if (!is_array($cfg)) {
|
||||
throw new RuntimeException('Invalid AI config format: expected array');
|
||||
}
|
||||
self::$configCache = $cfg;
|
||||
}
|
||||
|
||||
return self::$configCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an absolute URL from base_url and a path.
|
||||
*/
|
||||
private static function buildUrl(string $path, string $baseUrl): string
|
||||
{
|
||||
$trimmed = trim($path);
|
||||
if ($trimmed === '') {
|
||||
return $baseUrl;
|
||||
}
|
||||
if (str_starts_with($trimmed, 'http://') || str_starts_with($trimmed, 'https://')) {
|
||||
return $trimmed;
|
||||
}
|
||||
if ($trimmed[0] === '/') {
|
||||
return $baseUrl . $trimmed;
|
||||
}
|
||||
return $baseUrl . '/' . $trimmed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve status path based on configured responses_path and ai_request_id.
|
||||
*
|
||||
* @param int|string $aiRequestId
|
||||
* @param array<string,mixed> $cfg
|
||||
* @return string
|
||||
*/
|
||||
private static function resolveStatusPath($aiRequestId, array $cfg): string
|
||||
{
|
||||
$basePath = $cfg['responses_path'] ?? '';
|
||||
$trimmed = rtrim($basePath, '/');
|
||||
if ($trimmed === '') {
|
||||
return '/ai-request/' . rawurlencode((string)$aiRequestId) . '/status';
|
||||
}
|
||||
if (substr($trimmed, -11) !== '/ai-request') {
|
||||
$trimmed .= '/ai-request';
|
||||
}
|
||||
return $trimmed . '/' . rawurlencode((string)$aiRequestId) . '/status';
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared CURL sender for GET/POST requests.
|
||||
*
|
||||
* @param string $url
|
||||
* @param string $method
|
||||
* @param string|null $body
|
||||
* @param array<int,string> $headers
|
||||
* @param int $timeout
|
||||
* @param bool $verifyTls
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
private static function sendCurl(string $url, string $method, ?string $body, array $headers, int $timeout, bool $verifyTls): array
|
||||
{
|
||||
if (!function_exists('curl_init')) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'curl_missing',
|
||||
'message' => 'PHP cURL extension is missing. Install or enable it on the VM.',
|
||||
];
|
||||
}
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $verifyTls);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $verifyTls ? 2 : 0);
|
||||
curl_setopt($ch, CURLOPT_FAILONERROR, false);
|
||||
|
||||
$upper = strtoupper($method);
|
||||
if ($upper === 'POST') {
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $body ?? '');
|
||||
} else {
|
||||
curl_setopt($ch, CURLOPT_HTTPGET, true);
|
||||
}
|
||||
|
||||
$responseBody = curl_exec($ch);
|
||||
if ($responseBody === false) {
|
||||
$error = curl_error($ch) ?: 'Unknown cURL error';
|
||||
curl_close($ch);
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'curl_error',
|
||||
'message' => $error,
|
||||
];
|
||||
}
|
||||
|
||||
$status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
$decoded = null;
|
||||
if ($responseBody !== '' && $responseBody !== null) {
|
||||
$decoded = json_decode($responseBody, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
$decoded = null;
|
||||
}
|
||||
}
|
||||
|
||||
if ($status >= 200 && $status < 300) {
|
||||
return [
|
||||
'success' => true,
|
||||
'status' => $status,
|
||||
'data' => $decoded ?? $responseBody,
|
||||
];
|
||||
}
|
||||
|
||||
$errorMessage = 'AI proxy request failed';
|
||||
if (is_array($decoded)) {
|
||||
$errorMessage = $decoded['error'] ?? $decoded['message'] ?? $errorMessage;
|
||||
} elseif (is_string($responseBody) && $responseBody !== '') {
|
||||
$errorMessage = $responseBody;
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'status' => $status,
|
||||
'error' => $errorMessage,
|
||||
'response' => $decoded ?? $responseBody,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy alias for backward compatibility with the previous class name.
|
||||
if (!class_exists('OpenAIService')) {
|
||||
class_alias(LocalAIApi::class, 'OpenAIService');
|
||||
}
|
||||
@ -1,52 +0,0 @@
|
||||
<?php
|
||||
// OpenAI proxy configuration (workspace scope).
|
||||
// Reads values from environment variables or executor/.env.
|
||||
|
||||
$projectUuid = getenv('PROJECT_UUID');
|
||||
$projectId = getenv('PROJECT_ID');
|
||||
|
||||
if (
|
||||
($projectUuid === false || $projectUuid === null || $projectUuid === '') ||
|
||||
($projectId === false || $projectId === null || $projectId === '')
|
||||
) {
|
||||
$envPath = realpath(__DIR__ . '/../../.env'); // executor/.env
|
||||
if ($envPath && is_readable($envPath)) {
|
||||
$lines = @file($envPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [];
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
if ($line === '' || $line[0] === '#') {
|
||||
continue;
|
||||
}
|
||||
if (!str_contains($line, '=')) {
|
||||
continue;
|
||||
}
|
||||
[$key, $value] = array_map('trim', explode('=', $line, 2));
|
||||
if ($key === '') {
|
||||
continue;
|
||||
}
|
||||
$value = trim($value, "\"' ");
|
||||
if (getenv($key) === false || getenv($key) === '') {
|
||||
putenv("{$key}={$value}");
|
||||
}
|
||||
}
|
||||
$projectUuid = getenv('PROJECT_UUID');
|
||||
$projectId = getenv('PROJECT_ID');
|
||||
}
|
||||
}
|
||||
|
||||
$projectUuid = ($projectUuid === false) ? null : $projectUuid;
|
||||
$projectId = ($projectId === false) ? null : $projectId;
|
||||
|
||||
$baseUrl = 'https://flatlogic.com';
|
||||
$responsesPath = $projectId ? "/projects/{$projectId}/ai-request" : null;
|
||||
|
||||
return [
|
||||
'base_url' => $baseUrl,
|
||||
'responses_path' => $responsesPath,
|
||||
'project_id' => $projectId,
|
||||
'project_uuid' => $projectUuid,
|
||||
'project_header' => 'project-uuid',
|
||||
'default_model' => 'gpt-5-mini',
|
||||
'timeout' => 30,
|
||||
'verify_tls' => true,
|
||||
];
|
||||
@ -1,491 +1,228 @@
|
||||
/* AFG CARS Enterprise Style - 100% Offline */
|
||||
:root {
|
||||
--bg-color: #050505;
|
||||
--surface-color: #111111;
|
||||
--card-bg: #181818;
|
||||
--accent-color: #d4af37; /* Supreme Gold */
|
||||
--accent-hover: #f1c40f;
|
||||
--text-primary: #ffffff;
|
||||
--text-secondary: #a0a0a0;
|
||||
--border-radius: 12px;
|
||||
--glass-bg: rgba(5, 5, 5, 0.9);
|
||||
--border-color: rgba(212, 175, 55, 0.15);
|
||||
--primary: #e63946;
|
||||
--secondary: #1d3557;
|
||||
--accent: #457b9d;
|
||||
--light: #f1faee;
|
||||
--dark: #0f172a;
|
||||
--glass-bg: rgba(255, 255, 255, 0.05);
|
||||
--glass-border: rgba(255, 255, 255, 0.1);
|
||||
--shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-primary);
|
||||
background-color: var(--dark);
|
||||
color: var(--light);
|
||||
line-height: 1.6;
|
||||
overflow-x: hidden;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
header {
|
||||
background: var(--glass-bg);
|
||||
backdrop-filter: blur(15px);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding: 1rem 5%;
|
||||
a { text-decoration: none; color: inherit; transition: 0.3s; }
|
||||
ul { list-style: none; }
|
||||
|
||||
/* Components */
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 10px 20px;
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
.btn:hover { background: #c1121f; transform: translateY(-2px); }
|
||||
.btn-outline { background: transparent; border: 2px solid var(--primary); }
|
||||
.btn-outline:hover { background: var(--primary); }
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
/* Navbar */
|
||||
.navbar {
|
||||
background: rgba(15, 23, 42, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
border-bottom: 1px solid var(--glass-border);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
padding: 15px 0;
|
||||
}
|
||||
.navbar .container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 1.6rem;
|
||||
font-weight: 900;
|
||||
color: var(--accent-color);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.logo span {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
nav ul {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
gap: 2.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
nav a {
|
||||
text-decoration: none;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
nav a:hover, nav a.active {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.admin-link {
|
||||
background: rgba(212, 175, 55, 0.1);
|
||||
padding: 0.6rem 1.2rem !important;
|
||||
border-radius: 30px;
|
||||
color: var(--accent-color) !important;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
.logo { font-size: 1.5rem; font-weight: bold; color: var(--primary); }
|
||||
.nav-links { display: flex; gap: 20px; }
|
||||
.nav-links a:hover { color: var(--primary); }
|
||||
|
||||
/* Hero */
|
||||
.hero {
|
||||
min-height: 95vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 5rem 5%;
|
||||
text-align: center;
|
||||
background: linear-gradient(rgba(0,0,0,0.75), rgba(0,0,0,0.75)), url('../images/hero/bg.jpg');
|
||||
height: 80vh;
|
||||
background: linear-gradient(rgba(0,0,0,0.6), rgba(0,0,0,0.6)), url('../images/hero.jpg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-attachment: fixed;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: 5rem;
|
||||
font-weight: 900;
|
||||
margin-bottom: 1.5rem;
|
||||
line-height: 1;
|
||||
max-width: 1000px;
|
||||
}
|
||||
|
||||
.hero h1 span {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.hero p {
|
||||
font-size: 1.25rem;
|
||||
color: var(--text-secondary);
|
||||
max-width: 700px;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.hero-btns {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
/* Sections */
|
||||
.container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 8rem 5%;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
margin-bottom: 4rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.section-title h1, .section-title h2 {
|
||||
font-size: 3rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
|
||||
.section-title p {
|
||||
color: var(--text-secondary);
|
||||
font-size: 1.1rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
/* Grid System */
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
gap: 3rem;
|
||||
}
|
||||
|
||||
/* Cards */
|
||||
.car-card {
|
||||
background: var(--surface-color);
|
||||
border-radius: var(--border-radius);
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(255,255,255,0.05);
|
||||
transition: 0.4s cubic-bezier(0.165, 0.84, 0.44, 1);
|
||||
}
|
||||
|
||||
.car-card:hover {
|
||||
transform: translateY(-10px);
|
||||
border-color: var(--accent-color);
|
||||
}
|
||||
|
||||
.car-image {
|
||||
position: relative;
|
||||
height: 240px;
|
||||
}
|
||||
|
||||
.car-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.car-badge {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
left: 1rem;
|
||||
background: var(--accent-color);
|
||||
color: #000;
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 900;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.car-content {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.car-title {
|
||||
font-size: 1.4rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.car-price {
|
||||
font-size: 1.8rem;
|
||||
color: var(--accent-color);
|
||||
font-weight: 800;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.car-meta {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.car-meta i {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.installment-box {
|
||||
margin-top: 1.5rem;
|
||||
padding: 1rem;
|
||||
background: rgba(212, 175, 55, 0.05);
|
||||
border: 1px dashed var(--accent-color);
|
||||
border-radius: 8px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.installment-box strong {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn {
|
||||
padding: 1.2rem 3rem;
|
||||
background: var(--accent-color);
|
||||
color: #000;
|
||||
text-decoration: none;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
border-radius: var(--border-radius);
|
||||
border: 2px solid var(--accent-color);
|
||||
transition: 0.3s;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: var(--accent-hover);
|
||||
border-color: var(--accent-hover);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background: transparent;
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.btn-outline:hover {
|
||||
background: var(--accent-color);
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/* Feature Boxes (Why Choose Us) */
|
||||
.feature-box {
|
||||
padding: 3rem;
|
||||
background: var(--surface-color);
|
||||
border-radius: var(--border-radius);
|
||||
text-align: center;
|
||||
border: 1px solid rgba(255,255,255,0.03);
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.feature-box:hover {
|
||||
border-color: var(--accent-color);
|
||||
background: rgba(212, 175, 55, 0.02);
|
||||
}
|
||||
|
||||
.feature-box i {
|
||||
font-size: 3rem;
|
||||
color: var(--accent-color);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.feature-box h3 {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.feature-box p {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* Reviews */
|
||||
.review-card {
|
||||
background: var(--surface-color);
|
||||
padding: 2.5rem;
|
||||
border-radius: var(--border-radius);
|
||||
border: 1px solid rgba(255,255,255,0.05);
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.review-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.review-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.customer-photo {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background: var(--accent-color);
|
||||
color: #000;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 900;
|
||||
font-size: 1.2rem;
|
||||
text-align: center;
|
||||
}
|
||||
.hero h1 { font-size: 3rem; margin-bottom: 20px; }
|
||||
.hero p { font-size: 1.2rem; margin-bottom: 30px; opacity: 0.9; }
|
||||
|
||||
.stars {
|
||||
color: var(--accent-color);
|
||||
margin-bottom: 1.5rem;
|
||||
/* Cards */
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 30px;
|
||||
margin: 50px 0;
|
||||
}
|
||||
.card {
|
||||
background: var(--glass-bg);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
backdrop-filter: blur(4px);
|
||||
transition: 0.3s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.card:hover { transform: translateY(-5px); box-shadow: var(--shadow); }
|
||||
.card img { width: 100%; height: 200px; object-fit: cover; }
|
||||
.card-body { padding: 20px; flex-grow: 1; display: flex; flex-direction: column; }
|
||||
.card-title { font-size: 1.2rem; margin-bottom: 10px; }
|
||||
.card-price { color: var(--primary); font-size: 1.1rem; font-weight: bold; margin-bottom: 10px; }
|
||||
.card-meta { font-size: 0.9rem; color: #ccc; margin-bottom: 15px; }
|
||||
.card-actions { margin-top: auto; }
|
||||
.badge {
|
||||
background: var(--accent);
|
||||
padding: 3px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8rem;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
/* Locations */
|
||||
.location-card {
|
||||
background: var(--surface-color);
|
||||
padding: 2.5rem;
|
||||
border-radius: var(--border-radius);
|
||||
border: 1px solid var(--border-color);
|
||||
/* Forms */
|
||||
.form-group { margin-bottom: 15px; }
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background: rgba(255,255,255,0.05);
|
||||
border: 1px solid var(--glass-border);
|
||||
color: white;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.form-control:focus { outline: none; border-color: var(--primary); }
|
||||
|
||||
/* Auth Box */
|
||||
.auth-box {
|
||||
max-width: 400px;
|
||||
margin: 50px auto;
|
||||
padding: 40px;
|
||||
background: var(--glass-bg);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.location-card h3 {
|
||||
margin-bottom: 1.5rem;
|
||||
/* Admin Dashboard */
|
||||
.dashboard-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
color: var(--accent-color);
|
||||
min-height: calc(100vh - 60px);
|
||||
}
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
background: rgba(0,0,0,0.3);
|
||||
padding: 20px;
|
||||
border-right: 1px solid var(--glass-border);
|
||||
}
|
||||
.sidebar a {
|
||||
display: block;
|
||||
padding: 12px;
|
||||
margin-bottom: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.sidebar a.active, .sidebar a:hover {
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
}
|
||||
.main-content { padding: 30px; flex-grow: 1; overflow-y: auto; }
|
||||
.stat-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.stat-card {
|
||||
background: var(--glass-bg);
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
border: 1px solid var(--glass-border);
|
||||
}
|
||||
.stat-number { font-size: 2rem; font-weight: bold; color: var(--primary); }
|
||||
|
||||
.location-info p {
|
||||
margin-bottom: 1rem;
|
||||
color: var(--text-secondary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
/* Tables */
|
||||
.table-responsive { overflow-x: auto; }
|
||||
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
|
||||
th, td { padding: 12px; text-align: left; border-bottom: 1px solid var(--glass-border); }
|
||||
th { background: rgba(255,255,255,0.05); }
|
||||
tr:hover { background: rgba(255,255,255,0.02); }
|
||||
|
||||
/* Installment Calculator */
|
||||
.calculator {
|
||||
background: var(--glass-bg);
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
margin-top: 30px;
|
||||
border: 1px solid var(--glass-border);
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
footer {
|
||||
background: #000;
|
||||
padding: 8rem 5% 4rem;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.footer-container {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr 1.5fr;
|
||||
gap: 5rem;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto 5rem;
|
||||
}
|
||||
|
||||
.footer-section h3 {
|
||||
margin-bottom: 2rem;
|
||||
color: var(--text-primary);
|
||||
text-transform: uppercase;
|
||||
font-size: 1.1rem;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.footer-section ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.footer-section li {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.footer-section a {
|
||||
text-decoration: none;
|
||||
color: var(--text-secondary);
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.footer-section a:hover {
|
||||
color: var(--accent-color);
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.social-links {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.social-links a {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
background: var(--surface-color);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text-primary);
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.social-links a:hover {
|
||||
background: var(--accent-color);
|
||||
color: #000;
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
|
||||
.footer-bottom {
|
||||
background: rgba(0,0,0,0.5);
|
||||
padding: 40px 0;
|
||||
margin-top: auto;
|
||||
border-top: 1px solid var(--glass-border);
|
||||
text-align: center;
|
||||
padding-top: 4rem;
|
||||
border-top: 1px solid rgba(255,255,255,0.05);
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Forms */
|
||||
.form-group {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
/* Helpers */
|
||||
.text-center { text-align: center; }
|
||||
.mt-5 { margin-top: 3rem; }
|
||||
.mb-5 { margin-bottom: 3rem; }
|
||||
.text-danger { color: #e63946; }
|
||||
.text-success { color: #2a9d8f; }
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.8rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
/* Filter Bar */
|
||||
.filter-bar {
|
||||
background: var(--glass-bg);
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 30px;
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.form-group input, .form-group textarea {
|
||||
width: 100%;
|
||||
padding: 1.2rem;
|
||||
background: var(--bg-color);
|
||||
border: 1px solid rgba(255,255,255,0.1);
|
||||
border-radius: 8px;
|
||||
color: #fff;
|
||||
outline: none;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.form-group input:focus, .form-group textarea:focus {
|
||||
border-color: var(--accent-color);
|
||||
}
|
||||
|
||||
/* Section Grid for Subpages */
|
||||
.section-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1.2fr 1fr;
|
||||
gap: 5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.section-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.hero h1 {
|
||||
font-size: 3.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.footer-container {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.hero h1 {
|
||||
font-size: 2.8rem;
|
||||
}
|
||||
.section-title {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
.filter-bar select, .filter-bar input {
|
||||
padding: 8px;
|
||||
background: rgba(0,0,0,0.3);
|
||||
border: 1px solid var(--glass-border);
|
||||
color: white;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 17 KiB |
BIN
assets/images/hero.jpg
Normal file
|
After Width: | Height: | Size: 33 KiB |
@ -1,63 +0,0 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../includes/role_middleware.php';
|
||||
requireRole(['Customer', 'Buyer']);
|
||||
|
||||
require_once __DIR__ . '/../includes/header.php';
|
||||
$pdo = db();
|
||||
$userId = $_SESSION['user_id'];
|
||||
|
||||
// Get Installments
|
||||
$installments = $pdo->prepare("
|
||||
SELECT i.*, c.brand, c.model
|
||||
FROM installments i
|
||||
JOIN sales s ON i.sale_id = s.id
|
||||
JOIN cars c ON s.car_id = c.id
|
||||
WHERE s.user_id = ?
|
||||
");
|
||||
$installments->execute([$userId]);
|
||||
$myInstallments = $installments->fetchAll();
|
||||
?>
|
||||
|
||||
<div style="padding: 2rem;">
|
||||
<div class="page-header">
|
||||
<h1>My Dashboard</h1>
|
||||
<span>Welcome, <?php echo htmlspecialchars($_SESSION['username']); ?></span>
|
||||
</div>
|
||||
|
||||
<div style="background: var(--card-bg); padding: 2rem; border-radius: var(--border-radius); border: 1px solid var(--border-color); margin-bottom: 2rem;">
|
||||
<h3 style="color: var(--accent-color); margin-bottom: 1rem;">My Installment Plans</h3>
|
||||
<?php if ($myInstallments): ?>
|
||||
<table class="data-table" style="width: 100%; border-collapse: collapse;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="text-align: left; padding: 0.5rem;">Vehicle</th>
|
||||
<th style="text-align: left; padding: 0.5rem;">Total</th>
|
||||
<th style="text-align: left; padding: 0.5rem;">Paid</th>
|
||||
<th style="text-align: left; padding: 0.5rem;">Monthly</th>
|
||||
<th style="text-align: left; padding: 0.5rem;">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($myInstallments as $inst): ?>
|
||||
<tr>
|
||||
<td style="padding: 0.5rem; border-bottom: 1px solid var(--border-color);"><?php echo htmlspecialchars($inst['brand'] . ' ' . $inst['model']); ?></td>
|
||||
<td style="padding: 0.5rem; border-bottom: 1px solid var(--border-color);">$<?php echo number_format($inst['total_amount'], 2); ?></td>
|
||||
<td style="padding: 0.5rem; border-bottom: 1px solid var(--border-color);">$<?php echo number_format($inst['paid_amount'], 2); ?></td>
|
||||
<td style="padding: 0.5rem; border-bottom: 1px solid var(--border-color);">$<?php echo number_format($inst['monthly_payment'], 2); ?></td>
|
||||
<td style="padding: 0.5rem; border-bottom: 1px solid var(--border-color);">
|
||||
<span style="padding: 0.2rem 0.5rem; border-radius: 4px; background: rgba(255, 255, 255, 0.1);">
|
||||
<?php echo htmlspecialchars($inst['status']); ?>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php else: ?>
|
||||
<p style="color: var(--text-secondary);">You have no active installment plans.</p>
|
||||
<a href="/marketplace.php" class="btn-sm btn-primary" style="margin-top: 1rem; display: inline-block;">Browse Cars</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once __DIR__ . '/../includes/footer.php'; ?>
|
||||
118
car_detail.php
Normal file
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
// car_detail.php
|
||||
require_once 'includes/auth.php';
|
||||
require_once 'includes/header.php';
|
||||
global $pdo;
|
||||
|
||||
$id = $_GET['id'] ?? 0;
|
||||
$stmt = $pdo->prepare("SELECT * FROM cars WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$car = $stmt->fetch();
|
||||
|
||||
if (!$car) {
|
||||
die("<div class='container mt-5'><h1>Car not found</h1><a href='marketplace.php'>Back to Marketplace</a></div>");
|
||||
}
|
||||
|
||||
// Fetch Branch Info
|
||||
$stmt = $pdo->prepare("SELECT * FROM branches WHERE id = ?");
|
||||
$stmt->execute([$car['branch_id']]);
|
||||
$branch = $stmt->fetch();
|
||||
?>
|
||||
|
||||
<div class="container mt-5">
|
||||
<a href="marketplace.php" class="btn-outline" style="margin-bottom: 20px; display: inline-block;">← Back to Marketplace</a>
|
||||
|
||||
<div class="grid" style="grid-template-columns: 2fr 1fr; gap: 40px;">
|
||||
<!-- Left Column: Image & Desc -->
|
||||
<div>
|
||||
<div class="card" style="padding: 0; margin-bottom: 20px;">
|
||||
<img src="<?= htmlspecialchars($car['image_path'] ?? $car['image_url'] ?? '') ?>" alt="<?= htmlspecialchars($car['brand']) ?>" style="height: auto; max-height: 500px;">
|
||||
</div>
|
||||
|
||||
<div class="card" style="padding: 30px;">
|
||||
<h2>Vehicle Description</h2>
|
||||
<p><?= nl2br(htmlspecialchars($car['description'])) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Details & Actions -->
|
||||
<div>
|
||||
<div class="card" style="padding: 30px;">
|
||||
<h1 style="color: var(--primary); margin-bottom: 10px;">
|
||||
<?= htmlspecialchars($car['year'] . ' ' . $car['brand'] . ' ' . $car['model']) ?>
|
||||
</h1>
|
||||
<div style="font-size: 2rem; font-weight: bold; margin-bottom: 20px;">
|
||||
$<?= number_format((float)$car['price']) ?>
|
||||
</div>
|
||||
|
||||
<table style="margin-bottom: 30px;">
|
||||
<tr><th>Mileage</th><td><?= number_format((float)$car['mileage']) ?> km</td></tr>
|
||||
<tr><th>Fuel</th><td><?= htmlspecialchars($car['fuel_type']) ?></td></tr>
|
||||
<tr><th>Transmission</th><td><?= htmlspecialchars($car['transmission']) ?></td></tr>
|
||||
<tr><th>Status</th><td><?= htmlspecialchars(ucfirst($car['status'])) ?></td></tr>
|
||||
<tr><th>Location</th><td><?= htmlspecialchars($branch['city'] ?? 'Unknown') ?></td></tr>
|
||||
</table>
|
||||
|
||||
<a href="contact.php?inquiry=<?= $car['id'] ?>" class="btn" style="width: 100%; margin-bottom: 10px;">Request to Buy</a>
|
||||
<button onclick="document.getElementById('calc-section').scrollIntoView({behavior: 'smooth'})" class="btn-outline" style="width: 100%;">Calculate Installments</button>
|
||||
</div>
|
||||
|
||||
<div class="card" style="padding: 20px; margin-top: 20px;">
|
||||
<h3>Branch Contact</h3>
|
||||
<p><strong><?= htmlspecialchars($branch['name'] ?? '') ?></strong></p>
|
||||
<p><?= htmlspecialchars($branch['address'] ?? '') ?></p>
|
||||
<p><?= htmlspecialchars($branch['phone'] ?? '') ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Installment Calculator for this car -->
|
||||
<div id="calc-section" class="card mt-5" style="padding: 40px;">
|
||||
<h2>Installment Calculator</h2>
|
||||
<div class="grid" style="grid-template-columns: 1fr 1fr 1fr;">
|
||||
<div>
|
||||
<label>Vehicle Price</label>
|
||||
<input type="text" class="form-control" value="$<?= number_format((float)$car['price']) ?>" disabled>
|
||||
</div>
|
||||
<div>
|
||||
<label>Down Payment ($)</label>
|
||||
<input type="number" id="downPayment" class="form-control" value="<?= (float)$car['price'] * 0.2 ?>" oninput="calc()">
|
||||
</div>
|
||||
<div>
|
||||
<label>Term</label>
|
||||
<select id="term" class="form-control" onchange="calc()">
|
||||
<option value="12">12 Months</option>
|
||||
<option value="24" selected>24 Months</option>
|
||||
<option value="36">36 Months</option>
|
||||
<option value="48">48 Months</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 20px; font-size: 1.5rem; text-align: center;">
|
||||
Estimated Monthly Payment: <span id="monthlyPayment" style="color: var(--primary); font-weight: bold;">---</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function calc() {
|
||||
const price = <?= (float)$car['price'] ?>;
|
||||
const down = parseFloat(document.getElementById('downPayment').value) || 0;
|
||||
const months = parseInt(document.getElementById('term').value);
|
||||
|
||||
const principal = price - down;
|
||||
if (principal < 0) {
|
||||
document.getElementById('monthlyPayment').innerText = "Down payment exceeds price!";
|
||||
return;
|
||||
}
|
||||
|
||||
const interest = 0.05; // 5% flat
|
||||
const total = principal * (1 + interest);
|
||||
const monthly = total / months;
|
||||
|
||||
document.getElementById('monthlyPayment').innerText = "$" + monthly.toFixed(2);
|
||||
}
|
||||
calc(); // Run on load
|
||||
</script>
|
||||
|
||||
<?php require_once 'includes/footer.php'; ?>
|
||||
117
car_details.php
@ -1,117 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/includes/header.php';
|
||||
|
||||
$id = $_GET['id'] ?? 0;
|
||||
$car = null;
|
||||
|
||||
try {
|
||||
$db = db();
|
||||
$stmt = $db->prepare("SELECT c.*, b.name as branch_name, b.address as branch_address, b.phone as branch_phone
|
||||
FROM cars c
|
||||
LEFT JOIN branches b ON c.branch_id = b.id
|
||||
WHERE c.id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$car = $stmt->fetch();
|
||||
} catch (Exception $e) {
|
||||
// Log error if needed
|
||||
}
|
||||
|
||||
if (!$car) {
|
||||
echo "<div class='container' style='padding: 4rem; text-align: center;'>
|
||||
<h2>Car not found</h2>
|
||||
<p>The vehicle you are looking for does not exist or has been removed.</p>
|
||||
<a href='marketplace.php' class='btn'>Browse Inventory</a>
|
||||
</div>";
|
||||
require_once __DIR__ . '/includes/footer.php';
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
|
||||
<section class="container" style="padding-top: 4rem;">
|
||||
<div class="grid" style="grid-template-columns: 1.5fr 1fr; gap: 3rem; align-items: start;">
|
||||
|
||||
<!-- Left Column: Images -->
|
||||
<div>
|
||||
<div class="car-image" style="height: 500px; margin-bottom: 1rem;">
|
||||
<?php if ($car['is_featured']): ?>
|
||||
<span class="car-badge">FEATURED</span>
|
||||
<?php endif; ?>
|
||||
<img src="<?= htmlspecialchars($car['image_url']) ?>" alt="<?= htmlspecialchars($car['brand']) ?>" style="width: 100%; height: 100%; object-fit: cover; border-radius: var(--border-radius);">
|
||||
</div>
|
||||
<!-- Placeholder for additional gallery images if we had them -->
|
||||
<div class="grid" style="grid-template-columns: repeat(4, 1fr); gap: 1rem;">
|
||||
<!-- Just repeating main image for gallery effect since we only have one -->
|
||||
<img src="<?= htmlspecialchars($car['image_url']) ?>" style="height: 80px; width: 100%; object-fit: cover; border-radius: 8px; cursor: pointer; opacity: 0.7;">
|
||||
<img src="<?= htmlspecialchars($car['image_url']) ?>" style="height: 80px; width: 100%; object-fit: cover; border-radius: 8px; cursor: pointer; opacity: 0.7;">
|
||||
<img src="<?= htmlspecialchars($car['image_url']) ?>" style="height: 80px; width: 100%; object-fit: cover; border-radius: 8px; cursor: pointer; opacity: 0.7;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Details -->
|
||||
<div>
|
||||
<div style="margin-bottom: 1rem; color: var(--accent-color); font-weight: 700; text-transform: uppercase; letter-spacing: 1px;">
|
||||
<?= htmlspecialchars($car['status']) ?>
|
||||
</div>
|
||||
|
||||
<h1 style="font-size: 2.5rem; margin-bottom: 0.5rem; line-height: 1.2;">
|
||||
<?= htmlspecialchars($car['year'] . ' ' . $car['brand'] . ' ' . $car['model']) ?>
|
||||
</h1>
|
||||
|
||||
<div style="font-size: 2rem; font-weight: 800; color: var(--accent-color); margin-bottom: 2rem;">
|
||||
$<?= number_format((float)$car['price'], 0) ?>
|
||||
</div>
|
||||
|
||||
<div style="background: var(--surface-color); padding: 2rem; border-radius: var(--border-radius); border: 1px solid var(--border-color); margin-bottom: 2rem;">
|
||||
<h3 style="margin-bottom: 1.5rem; border-bottom: 1px solid var(--border-color); padding-bottom: 0.5rem;">Vehicle Specifications</h3>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem;">
|
||||
<div>
|
||||
<div style="color: var(--text-secondary); font-size: 0.9rem;">Mileage</div>
|
||||
<div style="font-weight: 600;"><?= number_format((float)$car['mileage'], 0) ?> km</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="color: var(--text-secondary); font-size: 0.9rem;">Transmission</div>
|
||||
<div style="font-weight: 600;"><?= htmlspecialchars($car['transmission'] ?? 'Automatic') ?></div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="color: var(--text-secondary); font-size: 0.9rem;">Fuel Type</div>
|
||||
<div style="font-weight: 600;"><?= htmlspecialchars($car['fuel_type'] ?? 'Petrol') ?></div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="color: var(--text-secondary); font-size: 0.9rem;">VIN</div>
|
||||
<div style="font-weight: 600;"><?= htmlspecialchars($car['vin']) ?></div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="color: var(--text-secondary); font-size: 0.9rem;">Location</div>
|
||||
<div style="font-weight: 600;"><?= htmlspecialchars($car['branch_name'] ?? 'Main Showroom') ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($car['installment_available']): ?>
|
||||
<div style="background: rgba(255, 215, 0, 0.1); padding: 1.5rem; border-radius: var(--border-radius); border: 1px solid var(--accent-color); margin-bottom: 2rem;">
|
||||
<h3 style="color: var(--accent-color); margin-bottom: 0.5rem;"><i class="fas fa-calculator"></i> Installment Plan</h3>
|
||||
<p style="margin-bottom: 1rem; font-size: 0.9rem;">Flexible financing options available for this vehicle.</p>
|
||||
<div style="font-size: 1.2rem; font-weight: 700;">
|
||||
Est. $<?= number_format($car['price'] / 60, 0) ?> / month
|
||||
<span style="font-size: 0.9rem; font-weight: 400; color: var(--text-secondary);">(60 months)</span>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div style="display: flex; gap: 1rem;">
|
||||
<a href="contact.php?subject=Purchase%20Inquiry%20-%20<?= urlencode($car['brand'] . ' ' . $car['model']) ?>&car_id=<?= $car['id'] ?>" class="btn" style="flex: 1; text-align: center;">Request to Buy</a>
|
||||
<?php if ($car['installment_available']): ?>
|
||||
<a href="contact.php?subject=Installment%20Inquiry%20-%20<?= urlencode($car['brand'] . ' ' . $car['model']) ?>&car_id=<?= $car['id'] ?>" class="btn btn-outline" style="flex: 1; text-align: center;">Apply for Installment</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<p style="margin-top: 1.5rem; font-size: 0.8rem; color: var(--text-secondary); text-align: center;">
|
||||
* Price excludes tax and registration fees. Contact us for final pricing.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
32
check_db.php
@ -1,32 +0,0 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->query("SHOW TABLES LIKE 'users'");
|
||||
if ($stmt->rowCount() > 0) {
|
||||
echo "Table 'users' exists.\n";
|
||||
} else {
|
||||
echo "Table 'users' does not exist.\n";
|
||||
// Create table
|
||||
$sql = "CREATE TABLE `users` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`username` varchar(100) NOT NULL,
|
||||
`email` varchar(100) NOT NULL UNIQUE,
|
||||
`password` varchar(255) NOT NULL,
|
||||
`role` enum('Guest','Customer','Dealer','Employee','Manager','Admin','Super Admin') DEFAULT 'Customer',
|
||||
`created_at` timestamp NULL DEFAULT current_timestamp(),
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `username` (`username`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;";
|
||||
$pdo->exec($sql);
|
||||
echo "Table 'users' created.\n";
|
||||
|
||||
// Seed admin user
|
||||
$password = password_hash('admin123', PASSWORD_DEFAULT);
|
||||
$stmt = $pdo->prepare("INSERT INTO users (username, email, password, role) VALUES (?, ?, ?, ?)");
|
||||
$stmt->execute(['admin', 'admin@example.com', $password, 'Admin']);
|
||||
echo "Admin user created (user: admin, pass: admin123).\n";
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
echo "Error: " . $e->getMessage();
|
||||
}
|
||||
132
contact.php
@ -1,88 +1,86 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/includes/header.php';
|
||||
// contact.php
|
||||
require_once 'includes/auth.php';
|
||||
require_once 'includes/header.php';
|
||||
global $pdo;
|
||||
|
||||
$subject = $_GET['subject'] ?? '';
|
||||
$car_id = $_GET['car_id'] ?? '';
|
||||
$message_placeholder = "Please provide details about the vehicle or service you are interested in...";
|
||||
$msg = '';
|
||||
$inquiry_car = null;
|
||||
|
||||
if ($car_id) {
|
||||
$message_placeholder = "I am interested in vehicle ID #$car_id. Please provide more information.";
|
||||
if (isset($_GET['inquiry'])) {
|
||||
$stmt = $pdo->prepare("SELECT * FROM cars WHERE id = ?");
|
||||
$stmt->execute([$_GET['inquiry']]);
|
||||
$inquiry_car = $stmt->fetch();
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// Process form (Demo only - just save to DB or show success)
|
||||
$name = $_POST['name'];
|
||||
$email = $_POST['email'];
|
||||
$message = $_POST['message'];
|
||||
$car_id = $_POST['car_id'] ?? null;
|
||||
|
||||
// Use the built-in mail service if desired, or just save to DB
|
||||
// For this "100% offline" task, saving to DB is better.
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO inquiries (car_id, name, email, message) VALUES (?, ?, ?, ?)");
|
||||
$stmt->execute([$car_id, $name, $email, $message]);
|
||||
|
||||
$msg = "Thank you! Your message has been sent. We will contact you shortly.";
|
||||
}
|
||||
?>
|
||||
|
||||
<section class="container" style="padding-top: 6rem;">
|
||||
<div class="section-title">
|
||||
<div>
|
||||
<h1>Connect With Us</h1>
|
||||
<p>Our specialists are ready to assist you with any inquiry.</p>
|
||||
<div class="container mt-5">
|
||||
<h1>Contact Us</h1>
|
||||
<p class="mb-5">We are here to help you find your dream car.</p>
|
||||
|
||||
<?php if ($msg): ?>
|
||||
<div style="background: rgba(42, 157, 143, 0.2); color: #2a9d8f; padding: 20px; border-radius: 10px; margin-bottom: 30px;">
|
||||
<?= htmlspecialchars($msg) ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-grid">
|
||||
<div style="background: var(--surface-color); padding: 3.5rem; border-radius: var(--border-radius); border: 1px solid rgba(255,255,255,0.05);">
|
||||
<h3 style="margin-bottom: 2.5rem; font-size: 1.8rem; color: var(--accent-color);">Direct Inquiry</h3>
|
||||
<form action="#" method="POST">
|
||||
<div class="grid" style="grid-template-columns: 1fr 1fr; gap: 2rem; margin-bottom: 0;">
|
||||
<div class="form-group">
|
||||
<label>First Name</label>
|
||||
<input type="text" placeholder="Ahmad" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Last Name</label>
|
||||
<input type="text" placeholder="Shah" required>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="grid" style="grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 50px;">
|
||||
<div>
|
||||
<form method="POST">
|
||||
<?php if ($inquiry_car): ?>
|
||||
<div style="background: rgba(255,255,255,0.05); padding: 15px; border-radius: 5px; margin-bottom: 20px;">
|
||||
<strong>Inquiry about:</strong> <?= htmlspecialchars($inquiry_car['year'] . ' ' . $inquiry_car['brand'] . ' ' . $inquiry_car['model']) ?>
|
||||
<input type="hidden" name="car_id" value="<?= $inquiry_car['id'] ?>">
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Your Name</label>
|
||||
<input type="text" name="name" class="form-control" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Email Address</label>
|
||||
<input type="email" placeholder="ahmad@domain.af" required>
|
||||
<input type="email" name="email" class="form-control" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Inquiry Subject</label>
|
||||
<select name="subject" style="width: 100%; padding: 1.2rem; background: var(--bg-color); border: 1px solid rgba(255,255,255,0.1); border-radius: 8px; color: white; outline: none; cursor: pointer;">
|
||||
<option value="Purchase Interest" <?= strpos($subject, 'Purchase') !== false ? 'selected' : '' ?>>Purchase Interest</option>
|
||||
<option value="Installment Inquiry" <?= strpos($subject, 'Installment') !== false ? 'selected' : '' ?>>Installment Inquiry</option>
|
||||
<option value="Vehicle Valuation" <?= strpos($subject, 'Valuation') !== false ? 'selected' : '' ?>>Vehicle Valuation (Selling)</option>
|
||||
<option value="Technical Support" <?= strpos($subject, 'Support') !== false ? 'selected' : '' ?>>Technical Support</option>
|
||||
</select>
|
||||
<label>Message</label>
|
||||
<textarea name="message" class="form-control" rows="5" required><?= $inquiry_car ? "I am interested in this vehicle. Please provide more details." : "" ?></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Your Message</label>
|
||||
<textarea name="message" rows="6" placeholder="<?= htmlspecialchars($message_placeholder) ?>" required></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn" style="width: 100%;">Send Message</button>
|
||||
<button type="submit" class="btn">Send Message</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; flex-direction: column; gap: 3rem;">
|
||||
<div class="location-card" style="border-style: solid;">
|
||||
<h3><i class="fas fa-headset"></i> Premium Support</h3>
|
||||
<div class="location-info">
|
||||
<p><i class="fas fa-phone"></i> +93 700 000 000</p>
|
||||
<p><i class="fas fa-envelope"></i> concierge@afgcars.af</p>
|
||||
<p><i class="fas fa-clock"></i> 24/7 VIP Assistance Available</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Head Office</h3>
|
||||
<p class="mb-5">Shar-e-Naw, Kabul, Afghanistan</p>
|
||||
|
||||
<div style="background: var(--surface-color); padding: 3rem; border-radius: var(--border-radius); border: 1px solid var(--border-color);">
|
||||
<h3 style="margin-bottom: 2rem; color: var(--accent-color);">Regional Centers</h3>
|
||||
<div style="display: flex; flex-direction: column; gap: 1.5rem;">
|
||||
<div style="border-bottom: 1px solid rgba(255,255,255,0.05); padding-bottom: 1rem;">
|
||||
<span style="color: var(--text-primary); font-weight: 800; display: block; margin-bottom: 0.3rem;">Kabul Headquarters</span>
|
||||
<p style="font-size: 0.85rem; color: var(--text-secondary);">Shar-e-Naw Business District, Plaza 4, 2nd Floor</p>
|
||||
</div>
|
||||
<div style="border-bottom: 1px solid rgba(255,255,255,0.05); padding-bottom: 1rem;">
|
||||
<span style="color: var(--text-primary); font-weight: 800; display: block; margin-bottom: 0.3rem;">Herat Regional Center</span>
|
||||
<p style="font-size: 0.85rem; color: var(--text-secondary);">Main Commercial Avenue, Herat Gate</p>
|
||||
</div>
|
||||
<div>
|
||||
<span style="color: var(--text-primary); font-weight: 800; display: block; margin-bottom: 0.3rem;">Mazar-i-Sharif Office</span>
|
||||
<p style="font-size: 0.85rem; color: var(--text-secondary);">Balkh Gate Towers, Suite 102</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h3>Phone</h3>
|
||||
<p class="mb-5">+93 700 000 000</p>
|
||||
|
||||
<h3>Email</h3>
|
||||
<p class="mb-5">info@afgcars.af</p>
|
||||
|
||||
<h3>Hours</h3>
|
||||
<p>Sat - Thu: 8:00 AM - 6:00 PM</p>
|
||||
<p>Friday: Closed</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
<?php require_once 'includes/footer.php'; ?>
|
||||
|
||||
@ -1,17 +1,22 @@
|
||||
<?php
|
||||
// Generated by setup_mariadb_project.sh — edit as needed.
|
||||
// db/config.php
|
||||
|
||||
// Database Configuration
|
||||
define('DB_HOST', '127.0.0.1');
|
||||
define('DB_NAME', 'app_38474');
|
||||
define('DB_USER', 'app_38474');
|
||||
define('DB_PASS', '31621ed0-58d1-46b5-b7d0-9eb1e1abacf7');
|
||||
|
||||
function db() {
|
||||
static $pdo;
|
||||
if (!$pdo) {
|
||||
$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]);
|
||||
}
|
||||
return $pdo;
|
||||
try {
|
||||
$pdo = new PDO("mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4", DB_USER, DB_PASS);
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
die("Database Connection Failed: " . $e->getMessage());
|
||||
}
|
||||
|
||||
// Global helper function if needed, but we can just use $pdo
|
||||
function getDB() {
|
||||
global $pdo;
|
||||
return $pdo;
|
||||
}
|
||||
|
||||
86
db/database.sql
Normal file
@ -0,0 +1,86 @@
|
||||
-- Database Schema for AFG_CARS Enterprise System
|
||||
SET FOREIGN_KEY_CHECKS=0;
|
||||
|
||||
DROP TABLE IF EXISTS `installments`;
|
||||
DROP TABLE IF EXISTS `sales`;
|
||||
DROP TABLE IF EXISTS `inquiries`;
|
||||
DROP TABLE IF EXISTS `cars`;
|
||||
DROP TABLE IF EXISTS `branches`;
|
||||
DROP TABLE IF EXISTS `users`;
|
||||
|
||||
-- Users Table
|
||||
CREATE TABLE `users` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`name` VARCHAR(100) NOT NULL,
|
||||
`email` VARCHAR(100) NOT NULL UNIQUE,
|
||||
`password` VARCHAR(255) NOT NULL,
|
||||
`role` ENUM('admin', 'user') DEFAULT 'user',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Branches Table
|
||||
CREATE TABLE `branches` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`name` VARCHAR(100) NOT NULL,
|
||||
`city` VARCHAR(50) NOT NULL,
|
||||
`address` TEXT,
|
||||
`phone` VARCHAR(20)
|
||||
);
|
||||
|
||||
-- Cars Table
|
||||
CREATE TABLE `cars` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`branch_id` INT,
|
||||
`brand` VARCHAR(50) NOT NULL,
|
||||
`model` VARCHAR(50) NOT NULL,
|
||||
`year` INT NOT NULL,
|
||||
`price` DECIMAL(10, 2) NOT NULL,
|
||||
`mileage` INT DEFAULT 0,
|
||||
`fuel_type` VARCHAR(20) DEFAULT 'Petrol',
|
||||
`transmission` VARCHAR(20) DEFAULT 'Automatic',
|
||||
`description` TEXT,
|
||||
`image_path` VARCHAR(255),
|
||||
`status` ENUM('available', 'sold') DEFAULT 'available',
|
||||
`is_featured` BOOLEAN DEFAULT 0,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`branch_id`) REFERENCES `branches`(`id`) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
-- Sales Table
|
||||
CREATE TABLE `sales` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`car_id` INT NOT NULL,
|
||||
`user_id` INT NOT NULL,
|
||||
`sale_date` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
`sale_price` DECIMAL(10, 2) NOT NULL,
|
||||
FOREIGN KEY (`car_id`) REFERENCES `cars`(`id`),
|
||||
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`)
|
||||
);
|
||||
|
||||
-- Installments Table
|
||||
CREATE TABLE `installments` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`car_id` INT NOT NULL,
|
||||
`user_id` INT NOT NULL,
|
||||
`total_amount` DECIMAL(10, 2) NOT NULL,
|
||||
`monthly_payment` DECIMAL(10, 2) NOT NULL,
|
||||
`months` INT NOT NULL,
|
||||
`status` ENUM('pending', 'approved', 'rejected', 'completed') DEFAULT 'pending',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`car_id`) REFERENCES `cars`(`id`),
|
||||
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`)
|
||||
);
|
||||
|
||||
-- Inquiries/Contact Table
|
||||
CREATE TABLE `inquiries` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`car_id` INT DEFAULT NULL,
|
||||
`user_id` INT DEFAULT NULL,
|
||||
`name` VARCHAR(100),
|
||||
`email` VARCHAR(100),
|
||||
`message` TEXT,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`car_id`) REFERENCES `cars`(`id`) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
SET FOREIGN_KEY_CHECKS=1;
|
||||
@ -1,44 +0,0 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../includes/role_middleware.php';
|
||||
requireRole('Dealer');
|
||||
|
||||
require_once __DIR__ . '/../includes/header.php';
|
||||
$pdo = db();
|
||||
$dealerId = $_SESSION['user_id'];
|
||||
|
||||
// Stats
|
||||
$myCars = $pdo->prepare("SELECT COUNT(*) FROM cars WHERE dealer_id = ?");
|
||||
$myCars->execute([$dealerId]);
|
||||
$myCarsCount = $myCars->fetchColumn();
|
||||
|
||||
$mySales = $pdo->prepare("SELECT COUNT(*) FROM sales s JOIN cars c ON s.car_id = c.id WHERE c.dealer_id = ?");
|
||||
$mySales->execute([$dealerId]);
|
||||
$mySalesCount = $mySales->fetchColumn();
|
||||
?>
|
||||
|
||||
<div style="padding: 2rem;">
|
||||
<div class="page-header">
|
||||
<h1>Dealer Dashboard</h1>
|
||||
<span>Welcome, <?php echo htmlspecialchars($_SESSION['username']); ?></span>
|
||||
</div>
|
||||
|
||||
<div class="card-grid" style="grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 2rem;">
|
||||
<div class="stat-card" style="padding: 1.5rem; background: var(--card-bg); border: 1px solid var(--border-color); border-radius: var(--border-radius);">
|
||||
<div style="font-size: 2rem; font-weight: bold;"><?php echo $myCarsCount; ?></div>
|
||||
<div style="color: var(--text-secondary);">My Inventory</div>
|
||||
</div>
|
||||
<div class="stat-card" style="padding: 1.5rem; background: var(--card-bg); border: 1px solid var(--border-color); border-radius: var(--border-radius);">
|
||||
<div style="font-size: 2rem; font-weight: bold;"><?php echo $mySalesCount; ?></div>
|
||||
<div style="color: var(--text-secondary);">My Sales</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="background: var(--card-bg); padding: 2rem; border-radius: var(--border-radius); border: 1px solid var(--border-color);">
|
||||
<h3>Manage Inventory</h3>
|
||||
<p>You can add new cars to your branch inventory here.</p>
|
||||
<a href="/admin/cars.php" class="btn-sm btn-primary">Go to Inventory Management</a>
|
||||
<!-- Ideally this would point to a dealer-specific car management page, but reusing admin/cars.php with permission checks is a good MVP step if implemented, otherwise a dedicated page is better. For now, let's keep it simple. -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once __DIR__ . '/../includes/footer.php'; ?>
|
||||
@ -1,51 +1,46 @@
|
||||
<?php
|
||||
session_start();
|
||||
// includes/auth.php
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
// Check if user is logged in
|
||||
function isLoggedIn() {
|
||||
return isset($_SESSION['user_id']);
|
||||
}
|
||||
|
||||
function isAdmin() {
|
||||
return isLoggedIn() && (
|
||||
$_SESSION['role'] === 'Admin' ||
|
||||
$_SESSION['role'] === 'Super Admin' ||
|
||||
$_SESSION['role'] === 'Manager'
|
||||
);
|
||||
// Get current user role
|
||||
function getUserRole() {
|
||||
return $_SESSION['user_role'] ?? 'guest';
|
||||
}
|
||||
|
||||
function requireLogin() {
|
||||
if (!isLoggedIn()) {
|
||||
header('Location: /login.php');
|
||||
exit;
|
||||
}
|
||||
// Get current user name
|
||||
function getUserName() {
|
||||
return $_SESSION['user_name'] ?? 'Guest';
|
||||
}
|
||||
|
||||
function requireAdmin() {
|
||||
requireLogin();
|
||||
if (!isAdmin()) {
|
||||
header('Location: /index.php');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
function login($username, $password) {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ? OR email = ?");
|
||||
$stmt->execute([$username, $username]);
|
||||
// Login function
|
||||
function login($email, $password) {
|
||||
global $pdo;
|
||||
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = ?");
|
||||
$stmt->execute([$email]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if ($user && password_verify($password, $user['password'])) {
|
||||
$_SESSION['user_id'] = $user['id'];
|
||||
$_SESSION['username'] = $user['username'];
|
||||
$_SESSION['role'] = $user['role'];
|
||||
$_SESSION['user_name'] = $user['name'];
|
||||
$_SESSION['user_email'] = $user['email'];
|
||||
$_SESSION['user_role'] = $user['role'];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Logout function
|
||||
function logout() {
|
||||
session_destroy();
|
||||
header('Location: /login.php');
|
||||
header("Location: login.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
@ -1,38 +1,37 @@
|
||||
<footer>
|
||||
<div class="footer-container">
|
||||
<div class="footer-section">
|
||||
<div class="logo">AFG_CARS<span>.</span></div>
|
||||
<p>Afghanistan's premier automotive marketplace. Luxury, reliability, and excellence in every drive.</p>
|
||||
<div class="social-links">
|
||||
<a href="#"><i class="fab fa-facebook"></i></a>
|
||||
<a href="#"><i class="fab fa-instagram"></i></a>
|
||||
<a href="#"><i class="fab fa-twitter"></i></a>
|
||||
<a href="#"><i class="fab fa-linkedin"></i></a>
|
||||
<?php
|
||||
// includes/footer.php
|
||||
?>
|
||||
<footer>
|
||||
<div class="container">
|
||||
<div class="grid" style="grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); text-align: left;">
|
||||
<div>
|
||||
<h3 style="color: var(--primary); margin-bottom: 15px;">AFG CARS</h3>
|
||||
<p>Afghanistan's premier automotive marketplace. Luxury, reliability, and excellence in every drive.</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 style="margin-bottom: 15px;">Quick Links</h3>
|
||||
<ul style="line-height: 2;">
|
||||
<li><a href="/index.php">Home</a></li>
|
||||
<li><a href="/marketplace.php">Marketplace</a></li>
|
||||
<li><a href="/work.php">How it Works</a></li>
|
||||
<li><a href="/about.php">About Us</a></li>
|
||||
<li><a href="/contact.php">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3 style="margin-bottom: 15px;">Contact Info</h3>
|
||||
<p>Shar-e-Naw, Kabul, Afghanistan</p>
|
||||
<p>+93 700 000 000</p>
|
||||
<p>info@afgcars.af</p>
|
||||
<p>Sat - Thu: 8:00 AM - 6:00 PM</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 50px; padding-top: 20px; border-top: 1px solid var(--glass-border); text-align: center; color: #888;">
|
||||
<p>© <?= date('Y') ?> AFG CARS - Elite Dealership System.</p>
|
||||
<p style="font-size: 0.8rem; margin-top: 5px;">100% Offline | Enterprise Ready</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h3>Quick Links</h3>
|
||||
<ul>
|
||||
<li><a href="index.php">Home</a></li>
|
||||
<li><a href="marketplace.php">Marketplace</a></li>
|
||||
<li><a href="work.php">How it Works</a></li>
|
||||
<li><a href="about.php">About Us</a></li>
|
||||
<li><a href="contact.php">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h3>Contact Info</h3>
|
||||
<p><i class="fas fa-map-marker-alt"></i> Shar-e-Naw, Kabul, Afghanistan</p>
|
||||
<p><i class="fas fa-phone"></i> +93 700 000 000</p>
|
||||
<p><i class="fas fa-envelope"></i> info@afgcars.af</p>
|
||||
<p><i class="fas fa-clock"></i> Sat - Thu: 8:00 AM - 6:00 PM</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© <?= date('Y') ?> AFG_CARS - Elite Dealership System. Developed by Mohammad Sadiq.</p>
|
||||
<p style="font-size: 0.8rem; color: var(--text-secondary); margin-top: 0.5rem;">100% Offline | Enterprise Ready | Professional UI</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -1,50 +1,40 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
require_once __DIR__ . '/init_db.php';
|
||||
ensure_db_setup();
|
||||
require_once __DIR__ . '/auth.php'; // Ensure session is started and auth helpers are available
|
||||
|
||||
$projectTitle = "AFG_CARS - Supreme Automotive";
|
||||
$projectDescription = "Elite car dealership management system. Premium marketplace, installments, and branch management.";
|
||||
$current_page = basename($_SERVER['PHP_SELF']);
|
||||
// includes/header.php
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
require_once __DIR__ . '/auth.php';
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?= htmlspecialchars($projectTitle) ?></title>
|
||||
<meta name="description" content="<?= htmlspecialchars($projectDescription) ?>">
|
||||
|
||||
<link rel="stylesheet" href="assets/css/style.css?v=<?= time() ?>">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
<title>AFG CARS Enterprise</title>
|
||||
<link rel="stylesheet" href="/assets/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<div class="logo">AFG_CARS<span>.</span></div>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="index.php" <?= $current_page == 'index.php' ? 'class="active"' : '' ?>>Home</a></li>
|
||||
<li><a href="marketplace.php" <?= $current_page == 'marketplace.php' ? 'class="active"' : '' ?>>Marketplace</a></li>
|
||||
<li><a href="work.php" <?= $current_page == 'work.php' ? 'class="active"' : '' ?>>Work</a></li>
|
||||
<li><a href="about.php" <?= $current_page == 'about.php' ? 'class="active"' : '' ?>>About</a></li>
|
||||
<li><a href="contact.php" <?= $current_page == 'contact.php' ? 'class="active"' : '' ?>>Contact Us</a></li>
|
||||
<?php if (isLoggedIn()): ?>
|
||||
<?php
|
||||
$dashboardLink = '/buyer/index.php'; // Default
|
||||
if (isAdmin()) {
|
||||
$dashboardLink = '/admin/index.php';
|
||||
} elseif (isset($_SESSION['role']) && $_SESSION['role'] === 'Dealer') {
|
||||
$dashboardLink = '/dealer/index.php';
|
||||
}
|
||||
?>
|
||||
<li><a href="<?= $dashboardLink ?>" class="admin-link"><i class="fas fa-chart-line"></i> Dashboard</a></li>
|
||||
<li><a href="/logout.php" class="admin-link"><i class="fas fa-sign-out-alt"></i> Logout</a></li>
|
||||
<?php else: ?>
|
||||
<li><a href="/login.php" class="admin-link"><i class="fas fa-user"></i> Login</a></li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
<nav class="navbar">
|
||||
<div class="container">
|
||||
<a href="/index.php" class="logo">AFG CARS</a>
|
||||
<div class="nav-links">
|
||||
<a href="/index.php">Home</a>
|
||||
<a href="/marketplace.php">Marketplace</a>
|
||||
<a href="/about.php">About</a>
|
||||
<a href="/work.php">Work</a>
|
||||
<a href="/contact.php">Contact</a>
|
||||
|
||||
<?php if (isLoggedIn()): ?>
|
||||
<?php if (getUserRole() === 'admin'): ?>
|
||||
<a href="/admin/index.php" class="btn">Admin</a>
|
||||
<?php else: ?>
|
||||
<a href="#" class="btn-outline">My Account</a>
|
||||
<?php endif; ?>
|
||||
<a href="/logout.php" style="color: #e63946;">Logout</a>
|
||||
<?php else: ?>
|
||||
<a href="/login.php" class="btn">Login</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
19
includes/middleware.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
// includes/middleware.php
|
||||
|
||||
require_once __DIR__ . '/auth.php';
|
||||
|
||||
function requireLogin() {
|
||||
if (!isLoggedIn()) {
|
||||
header("Location: /login.php");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
function requireAdmin() {
|
||||
requireLogin();
|
||||
if (getUserRole() !== 'admin') {
|
||||
header("Location: /index.php"); // Or dashboard
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
<?php
|
||||
function pexels_key() {
|
||||
$k = getenv('PEXELS_KEY');
|
||||
return $k && strlen($k) > 0 ? $k : 'Vc99rnmOhHhJAbgGQoKLZtsaIVfkeownoQNbTj78VemUjKh08ZYRbf18';
|
||||
}
|
||||
|
||||
function pexels_get($url) {
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => [ 'Authorization: '. pexels_key() ],
|
||||
CURLOPT_TIMEOUT => 15,
|
||||
]);
|
||||
$resp = curl_exec($ch);
|
||||
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
if ($code >= 200 && $code < 300 && $resp) return json_decode($resp, true);
|
||||
return null;
|
||||
}
|
||||
|
||||
function download_to($srcUrl, $destPath) {
|
||||
$data = file_get_contents($srcUrl);
|
||||
if ($data === false) return false;
|
||||
if (!is_dir(dirname($destPath))) mkdir(dirname($destPath), 0775, true);
|
||||
return file_put_contents($destPath, $data) !== false;
|
||||
}
|
||||
222
index.php
@ -1,94 +1,79 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/includes/header.php';
|
||||
// index.php
|
||||
require_once 'includes/auth.php';
|
||||
require_once 'includes/header.php';
|
||||
global $pdo;
|
||||
|
||||
// Fetch Featured Cars for Home Page
|
||||
// Fetch Featured Cars
|
||||
try {
|
||||
$db = db();
|
||||
$featured_cars = $db->query("SELECT c.*, b.name as branch_name
|
||||
FROM cars c
|
||||
JOIN branches b ON c.branch_id = b.id
|
||||
WHERE c.status = 'Available' AND c.is_featured = 1
|
||||
ORDER BY c.created_at DESC
|
||||
LIMIT 6")->fetchAll();
|
||||
|
||||
$branches = $db->query("SELECT * FROM branches LIMIT 3")->fetchAll();
|
||||
$stmt = $pdo->query("SELECT * FROM cars WHERE is_featured = 1 LIMIT 6");
|
||||
$featured_cars = $stmt->fetchAll();
|
||||
|
||||
// Fetch branches for selector
|
||||
$branches = $pdo->query("SELECT * FROM branches")->fetchAll();
|
||||
} catch (Exception $e) {
|
||||
$featured_cars = [];
|
||||
$branches = [];
|
||||
}
|
||||
?>
|
||||
|
||||
<!-- 1. & 2. Hero Section & Background Design -->
|
||||
<!-- Hero Section -->
|
||||
<section class="hero">
|
||||
<h1>Experience <span>Supreme</span> Luxury Automotive</h1>
|
||||
<p>Afghanistan's premier destination for elite vehicles, flexible installments, and professional service across our major branches.</p>
|
||||
<div class="hero-btns">
|
||||
<a href="marketplace.php" class="btn">View Cars</a>
|
||||
<a href="contact.php" class="btn btn-outline">Sell Your Car</a>
|
||||
<div class="container">
|
||||
<h1>Find Your Dream Car</h1>
|
||||
<p>Premium Vehicles. Flexible Installments. Nationwide Service.</p>
|
||||
<a href="marketplace.php" class="btn">Browse Inventory</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 6. Why Choose Us Section -->
|
||||
<section class="container">
|
||||
<div class="section-title">
|
||||
<div>
|
||||
<h2>Why Choose Us</h2>
|
||||
<p>Our commitment to excellence makes us the market leader.</p>
|
||||
<!-- Installment Calculator Section -->
|
||||
<section class="container mt-5">
|
||||
<h2 class="text-center">Installment Calculator</h2>
|
||||
<div class="calculator">
|
||||
<div class="grid" style="grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));">
|
||||
<div class="form-group">
|
||||
<label>Car Price ($)</label>
|
||||
<input type="number" id="calcPrice" class="form-control" placeholder="30000" value="30000">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Down Payment ($)</label>
|
||||
<input type="number" id="calcDown" class="form-control" placeholder="5000" value="5000">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Loan Term</label>
|
||||
<select id="calcMonths" class="form-control">
|
||||
<option value="12">12 Months</option>
|
||||
<option value="24" selected>24 Months</option>
|
||||
<option value="36">36 Months</option>
|
||||
<option value="48">48 Months</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" style="display: flex; align-items: flex-end;">
|
||||
<button onclick="calculateInstallment()" class="btn" style="width: 100%;">Calculate</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<div class="feature-box">
|
||||
<i class="fas fa-certificate"></i>
|
||||
<h3>Trusted Dealership</h3>
|
||||
<p>Over 10 years of excellence in the automotive industry with thousands of happy clients.</p>
|
||||
</div>
|
||||
<div class="feature-box">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
<h3>Verified Listings</h3>
|
||||
<p>Every vehicle undergoes a rigorous 150-point technical and background inspection.</p>
|
||||
</div>
|
||||
<div class="feature-box">
|
||||
<i class="fas fa-shield-alt"></i>
|
||||
<h3>Secure Transactions</h3>
|
||||
<p>Safe, transparent, and legally binding payment processes for all vehicle sales.</p>
|
||||
</div>
|
||||
<div class="feature-box">
|
||||
<i class="fas fa-bolt"></i>
|
||||
<h3>Fast Approval</h3>
|
||||
<p>Get your luxury car today with instant installment approval and minimal paperwork.</p>
|
||||
<div id="calcResult" class="mt-5 text-center" style="font-size: 1.5rem; font-weight: bold; color: var(--primary);">
|
||||
Monthly Payment: $1,145.83
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 5. Featured Cars Section -->
|
||||
<section class="container" style="background: var(--surface-color);">
|
||||
<div class="section-title">
|
||||
<div>
|
||||
<h2>Featured Collection</h2>
|
||||
<p>Hand-picked premium vehicles currently available in our showrooms.</p>
|
||||
</div>
|
||||
<a href="marketplace.php" class="btn btn-outline" style="padding: 0.8rem 1.5rem;">View All <i class="fas fa-arrow-right" style="margin-left: 10px;"></i></a>
|
||||
</div>
|
||||
<!-- Featured Cars -->
|
||||
<section class="container mt-5">
|
||||
<h2 class="text-center mb-5">Featured Vehicles</h2>
|
||||
<div class="grid">
|
||||
<?php foreach ($featured_cars as $car): ?>
|
||||
<div class="car-card">
|
||||
<div class="car-image">
|
||||
<span class="car-badge">PREMIUM</span>
|
||||
<img src="<?= htmlspecialchars($car['image_url']) ?>" alt="<?= htmlspecialchars($car['brand']) ?>">
|
||||
</div>
|
||||
<div class="car-content">
|
||||
<div class="car-title"><?= htmlspecialchars($car['brand'] . ' ' . $car['model']) ?></div>
|
||||
<div class="car-price">$<?= number_format((float)$car['price'], 0) ?></div>
|
||||
<div class="car-meta">
|
||||
<span><i class="fas fa-calendar-alt"></i> <?= $car['year'] ?></span>
|
||||
<span><i class="fas fa-map-marker-alt"></i> <?= htmlspecialchars($car['branch_name']) ?></span>
|
||||
<div class="card">
|
||||
<img src="<?= htmlspecialchars($car['image_path'] ?? $car['image_url'] ?? '') ?>" alt="<?= htmlspecialchars($car['brand']) ?>">
|
||||
<div class="card-body">
|
||||
<h3 class="card-title"><?= htmlspecialchars($car['brand'] . ' ' . $car['model']) ?></h3>
|
||||
<div class="card-price">$<?= number_format((float)$car['price']) ?></div>
|
||||
<div class="card-meta">
|
||||
<span><?= $car['year'] ?></span> •
|
||||
<span><?= number_format((float)$car['mileage']) ?> km</span>
|
||||
</div>
|
||||
<div class="installment-box">
|
||||
Installments from <strong>$<?= number_format($car['price'] / 60, 0) ?>/mo</strong>
|
||||
</div>
|
||||
<div style="margin-top: 1.5rem;">
|
||||
<a href="car_details.php?id=<?= $car['id'] ?>" class="btn" style="width: 100%; padding: 0.8rem;">View Details</a>
|
||||
<div class="card-actions">
|
||||
<a href="car_detail.php?id=<?= $car['id'] ?>" class="btn btn-outline" style="width:100%">View Details</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -96,78 +81,37 @@ try {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 3. Review Section -->
|
||||
<section class="container">
|
||||
<div class="section-title">
|
||||
<div>
|
||||
<h2>Customer Reviews</h2>
|
||||
<p>What our elite clientele says about their experience with AFG_CARS.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<div class="review-card">
|
||||
<div class="review-header">
|
||||
<div class="customer-photo">AS</div>
|
||||
<div>
|
||||
<div style="font-weight: 800; font-size: 1.1rem;">Ahmad Shah</div>
|
||||
<div style="font-size: 0.8rem; color: var(--accent-color);">Verified Buyer</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stars">
|
||||
<i class="fas fa-star"></i><i class="fas fa-star"></i><i class="fas fa-star"></i><i class="fas fa-star"></i><i class="fas fa-star"></i>
|
||||
</div>
|
||||
<p style="color: var(--text-secondary);">"The best experience I've had buying a car in Kabul. The installment plan was very easy to understand and the team was extremely professional. Highly recommended!"</p>
|
||||
</div>
|
||||
<div class="review-card">
|
||||
<div class="review-header">
|
||||
<div class="customer-photo">MK</div>
|
||||
<div>
|
||||
<div style="font-weight: 800; font-size: 1.1rem;">Mariam Khan</div>
|
||||
<div style="font-size: 0.8rem; color: var(--accent-color);">Business Owner</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stars">
|
||||
<i class="fas fa-star"></i><i class="fas fa-star"></i><i class="fas fa-star"></i><i class="fas fa-star"></i><i class="fas fa-star"></i>
|
||||
</div>
|
||||
<p style="color: var(--text-secondary);">"Found my dream Lexus LX 600 here. The condition was exactly as described. The Herat branch team was very helpful with all the paperwork. 5 stars!"</p>
|
||||
</div>
|
||||
<div class="review-card">
|
||||
<div class="review-header">
|
||||
<div class="customer-photo">RZ</div>
|
||||
<div>
|
||||
<div style="font-weight: 800; font-size: 1.1rem;">Reza Zaki</div>
|
||||
<div style="font-size: 0.8rem; color: var(--accent-color);">Verified Buyer</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stars">
|
||||
<i class="fas fa-star"></i><i class="fas fa-star"></i><i class="fas fa-star"></i><i class="fas fa-star"></i><i class="fas fa-star"></i>
|
||||
</div>
|
||||
<p style="color: var(--text-secondary);">"Transparent pricing and high-quality inventory. I appreciate the technical reports they provided before I made my decision. Best dealership in Afghanistan."</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 4. Physical Location Section -->
|
||||
<section class="container" style="background: var(--surface-color);">
|
||||
<div class="section-title">
|
||||
<div>
|
||||
<h2>Dealership Locations</h2>
|
||||
<p>Visit us at any of our modern showrooms across Afghanistan.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<!-- Branch Selector -->
|
||||
<section class="container mt-5 mb-5">
|
||||
<h2 class="text-center mb-5">Visit Our Branches</h2>
|
||||
<div class="grid" style="grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));">
|
||||
<?php foreach ($branches as $branch): ?>
|
||||
<div class="location-card">
|
||||
<h3><i class="fas fa-city"></i> <?= htmlspecialchars($branch['city']) ?></h3>
|
||||
<div class="location-info">
|
||||
<p><i class="fas fa-map-marker-alt"></i> <?= htmlspecialchars($branch['address']) ?></p>
|
||||
<p><i class="fas fa-phone"></i> <?= htmlspecialchars($branch['phone']) ?></p>
|
||||
<p><i class="fas fa-clock"></i> <?= htmlspecialchars($branch['hours']) ?></p>
|
||||
</div>
|
||||
<a href="https://maps.google.com" target="_blank" class="btn btn-outline" style="width: 100%; margin-top: 1rem; padding: 0.8rem;">Open Map</a>
|
||||
<div class="card" style="padding: 20px; text-align: center;">
|
||||
<h3 style="color: var(--primary); margin-bottom: 10px;"><?= htmlspecialchars($branch['city']) ?></h3>
|
||||
<p style="margin-bottom: 5px;"><?= htmlspecialchars($branch['name']) ?></p>
|
||||
<p style="color: #aaa;"><?= htmlspecialchars($branch['phone']) ?></p>
|
||||
<a href="contact.php?branch=<?= $branch['id'] ?>" class="btn-outline" style="margin-top: 15px; display: inline-block;">View Map</a>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
<script>
|
||||
function calculateInstallment() {
|
||||
const price = parseFloat(document.getElementById('calcPrice').value);
|
||||
const down = parseFloat(document.getElementById('calcDown').value);
|
||||
const months = parseInt(document.getElementById('calcMonths').value);
|
||||
|
||||
if (price && months) {
|
||||
const principal = price - (down || 0);
|
||||
const interestRate = 0.05; // 5% flat rate
|
||||
const total = principal * (1 + interestRate);
|
||||
const monthly = total / months;
|
||||
|
||||
document.getElementById('calcResult').innerText =
|
||||
`Monthly Payment: $${monthly.toFixed(2)}`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php require_once 'includes/footer.php'; ?>
|
||||
|
||||
124
login.php
@ -1,41 +1,27 @@
|
||||
<?php
|
||||
// login.php
|
||||
require_once 'includes/auth.php';
|
||||
|
||||
if (isLoggedIn()) {
|
||||
header("Location: index.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
$error = '';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$username = $_POST['username'] ?? '';
|
||||
$email = $_POST['email'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
|
||||
if (login($username, $password)) {
|
||||
$role = $_SESSION['role'] ?? 'Customer';
|
||||
|
||||
// Log login attempt
|
||||
try {
|
||||
$pdo = db();
|
||||
$pdo->prepare("INSERT INTO activity_logs (user_id, action, ip_address) VALUES (?, 'Login', ?)")
|
||||
->execute([$_SESSION['user_id'], $_SERVER['REMOTE_ADDR']]);
|
||||
} catch (Exception $e) { /* Ignore logging error */ }
|
||||
|
||||
switch ($role) {
|
||||
case 'Admin':
|
||||
case 'Super Admin':
|
||||
case 'Manager':
|
||||
header('Location: /admin/index.php');
|
||||
break;
|
||||
case 'Dealer':
|
||||
header('Location: /dealer/index.php');
|
||||
break;
|
||||
case 'Customer':
|
||||
case 'Buyer':
|
||||
header('Location: /buyer/index.php');
|
||||
break;
|
||||
default:
|
||||
header('Location: /index.php');
|
||||
if (login($email, $password)) {
|
||||
if (getUserRole() === 'admin') {
|
||||
header("Location: admin/index.php");
|
||||
} else {
|
||||
header("Location: index.php");
|
||||
}
|
||||
exit;
|
||||
} else {
|
||||
$error = "Invalid username or password";
|
||||
$error = "Invalid email or password";
|
||||
}
|
||||
}
|
||||
?>
|
||||
@ -44,81 +30,35 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Login - Car Market</title>
|
||||
<title>Login - AFG CARS</title>
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
<style>
|
||||
body { display: flex; align-items: center; justify-content: center; min-height: 100vh; }
|
||||
.login-container {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
padding: 2.5rem;
|
||||
background: var(--card-bg);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,0.4);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
.form-group { margin-bottom: 1.5rem; }
|
||||
label { display: block; margin-bottom: 0.5rem; color: var(--text-secondary); font-size: 0.9rem; }
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 0.8rem;
|
||||
background: var(--bg-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
color: var(--text-primary);
|
||||
font-size: 1rem;
|
||||
}
|
||||
input:focus { outline: none; border-color: var(--accent-color); }
|
||||
button {
|
||||
width: 100%;
|
||||
padding: 0.8rem;
|
||||
background: var(--accent-color);
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
color: var(--bg-color);
|
||||
font-weight: 700;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
button:hover { background: var(--accent-hover); transform: translateY(-1px); }
|
||||
.error {
|
||||
background: rgba(255, 68, 68, 0.1);
|
||||
color: #ff4444;
|
||||
padding: 0.8rem;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 1.5rem;
|
||||
text-align: center;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
h2 { text-align: center; margin-bottom: 2rem; color: var(--accent-color); font-size: 1.8rem; }
|
||||
.links { text-align: center; margin-top: 1.5rem; font-size: 0.9rem; color: var(--text-secondary); }
|
||||
.links a { color: var(--accent-color); text-decoration: none; font-weight: 600; }
|
||||
.links a:hover { text-decoration: underline; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-container">
|
||||
<h2>Welcome Back</h2>
|
||||
<body style="display: flex; align-items: center; justify-content: center; min-height: 100vh;">
|
||||
|
||||
<div class="auth-box">
|
||||
<h1 class="logo mb-5">AFG CARS</h1>
|
||||
<h2 class="mb-5">Login</h2>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="error"><?php echo htmlspecialchars($error); ?></div>
|
||||
<div style="background: rgba(230, 57, 70, 0.2); color: #e63946; padding: 10px; border-radius: 5px; margin-bottom: 20px;">
|
||||
<?= htmlspecialchars($error) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="POST">
|
||||
<div class="form-group">
|
||||
<label>Username or Email</label>
|
||||
<input type="text" name="username" required placeholder="Enter your username">
|
||||
<input type="email" name="email" class="form-control" placeholder="Email Address" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Password</label>
|
||||
<input type="password" name="password" required placeholder="Enter your password">
|
||||
</div>
|
||||
<button type="submit">Sign In</button>
|
||||
<div class="links">
|
||||
Don't have an account? <a href="register.php">Create Account</a><br>
|
||||
<a href="/index.php" style="font-size: 0.8rem; opacity: 0.7;">Back to Home</a>
|
||||
<input type="password" name="password" class="form-control" placeholder="Password" required>
|
||||
</div>
|
||||
<button type="submit" class="btn" style="width: 100%;">Sign In</button>
|
||||
</form>
|
||||
|
||||
<p class="mt-5">
|
||||
<a href="index.php" style="color: #aaa;">Back to Home</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -1,235 +0,0 @@
|
||||
<?php
|
||||
// Minimal mail service for the workspace app (VM).
|
||||
// Usage:
|
||||
// require_once __DIR__ . '/MailService.php';
|
||||
// // Generic:
|
||||
// MailService::sendMail($to, $subject, $htmlBody, $textBody = null, $opts = []);
|
||||
// // Contact form helper:
|
||||
// MailService::sendContactMessage($name, $email, $message, $to = null, $subject = 'New contact form');
|
||||
|
||||
class MailService
|
||||
{
|
||||
// Universal mail sender (no attachments by design)
|
||||
public static function sendMail($to, string $subject, string $htmlBody, ?string $textBody = null, array $opts = [])
|
||||
{
|
||||
$cfg = self::loadConfig();
|
||||
|
||||
$autoload = __DIR__ . '/../vendor/autoload.php';
|
||||
if (file_exists($autoload)) {
|
||||
require_once $autoload;
|
||||
}
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
@require_once 'libphp-phpmailer/autoload.php';
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
@require_once 'libphp-phpmailer/src/Exception.php';
|
||||
@require_once 'libphp-phpmailer/src/SMTP.php';
|
||||
@require_once 'libphp-phpmailer/src/PHPMailer.php';
|
||||
}
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
@require_once 'PHPMailer/src/Exception.php';
|
||||
@require_once 'PHPMailer/src/SMTP.php';
|
||||
@require_once 'PHPMailer/src/PHPMailer.php';
|
||||
}
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
@require_once 'PHPMailer/Exception.php';
|
||||
@require_once 'PHPMailer/SMTP.php';
|
||||
@require_once 'PHPMailer/PHPMailer.php';
|
||||
}
|
||||
}
|
||||
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
return [ 'success' => false, 'error' => 'PHPMailer not available' ];
|
||||
}
|
||||
|
||||
$mail = new PHPMailer\PHPMailer\PHPMailer(true);
|
||||
try {
|
||||
$mail->isSMTP();
|
||||
$mail->Host = $cfg['smtp_host'] ?? '';
|
||||
$mail->Port = (int)($cfg['smtp_port'] ?? 587);
|
||||
$secure = $cfg['smtp_secure'] ?? 'tls';
|
||||
if ($secure === 'ssl') $mail->SMTPSecure = PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_SMTPS;
|
||||
elseif ($secure === 'tls') $mail->SMTPSecure = PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_STARTTLS;
|
||||
else $mail->SMTPSecure = false;
|
||||
$mail->SMTPAuth = true;
|
||||
$mail->Username = $cfg['smtp_user'] ?? '';
|
||||
$mail->Password = $cfg['smtp_pass'] ?? '';
|
||||
|
||||
$fromEmail = $opts['from_email'] ?? ($cfg['from_email'] ?? 'no-reply@localhost');
|
||||
$fromName = $opts['from_name'] ?? ($cfg['from_name'] ?? 'App');
|
||||
$mail->setFrom($fromEmail, $fromName);
|
||||
if (!empty($opts['reply_to']) && filter_var($opts['reply_to'], FILTER_VALIDATE_EMAIL)) {
|
||||
$mail->addReplyTo($opts['reply_to']);
|
||||
} elseif (!empty($cfg['reply_to'])) {
|
||||
$mail->addReplyTo($cfg['reply_to']);
|
||||
}
|
||||
|
||||
// Recipients
|
||||
$toList = [];
|
||||
if ($to) {
|
||||
if (is_string($to)) $toList = array_map('trim', explode(',', $to));
|
||||
elseif (is_array($to)) $toList = $to;
|
||||
} elseif (!empty(getenv('MAIL_TO'))) {
|
||||
$toList = array_map('trim', explode(',', getenv('MAIL_TO')));
|
||||
}
|
||||
$added = 0;
|
||||
foreach ($toList as $addr) {
|
||||
if (filter_var($addr, FILTER_VALIDATE_EMAIL)) { $mail->addAddress($addr); $added++; }
|
||||
}
|
||||
if ($added === 0) {
|
||||
return [ 'success' => false, 'error' => 'No recipients defined (set MAIL_TO or pass $to)' ];
|
||||
}
|
||||
|
||||
foreach ((array)($opts['cc'] ?? []) as $cc) { if (filter_var($cc, FILTER_VALIDATE_EMAIL)) $mail->addCC($cc); }
|
||||
foreach ((array)($opts['bcc'] ?? []) as $bcc){ if (filter_var($bcc, FILTER_VALIDATE_EMAIL)) $mail->addBCC($bcc); }
|
||||
|
||||
// Optional DKIM
|
||||
if (!empty($cfg['dkim_domain']) && !empty($cfg['dkim_selector']) && !empty($cfg['dkim_private_key_path'])) {
|
||||
$mail->DKIM_domain = $cfg['dkim_domain'];
|
||||
$mail->DKIM_selector = $cfg['dkim_selector'];
|
||||
$mail->DKIM_private = $cfg['dkim_private_key_path'];
|
||||
}
|
||||
|
||||
$mail->isHTML(true);
|
||||
$mail->Subject = $subject;
|
||||
$mail->Body = $htmlBody;
|
||||
$mail->AltBody = $textBody ?? strip_tags($htmlBody);
|
||||
$ok = $mail->send();
|
||||
return [ 'success' => $ok ];
|
||||
} catch (\Throwable $e) {
|
||||
return [ 'success' => false, 'error' => 'PHPMailer error: ' . $e->getMessage() ];
|
||||
}
|
||||
}
|
||||
private static function loadConfig(): array
|
||||
{
|
||||
$configPath = __DIR__ . '/config.php';
|
||||
if (!file_exists($configPath)) {
|
||||
throw new \RuntimeException('Mail config not found. Copy mail/config.sample.php to mail/config.php and fill in credentials.');
|
||||
}
|
||||
$cfg = require $configPath;
|
||||
if (!is_array($cfg)) {
|
||||
throw new \RuntimeException('Invalid mail config format: expected array');
|
||||
}
|
||||
return $cfg;
|
||||
}
|
||||
|
||||
// Send a contact message
|
||||
// $to can be: a single email string, a comma-separated list, an array of emails, or null (fallback to MAIL_TO/MAIL_FROM)
|
||||
public static function sendContactMessage(string $name, string $email, string $message, $to = null, string $subject = 'New contact form')
|
||||
{
|
||||
$cfg = self::loadConfig();
|
||||
|
||||
// Try Composer autoload if available (for PHPMailer)
|
||||
$autoload = __DIR__ . '/../vendor/autoload.php';
|
||||
if (file_exists($autoload)) {
|
||||
require_once $autoload;
|
||||
}
|
||||
// Fallback to system-wide PHPMailer (installed via apt: libphp-phpmailer)
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
// Debian/Ubuntu package layout (libphp-phpmailer)
|
||||
@require_once 'libphp-phpmailer/autoload.php';
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
@require_once 'libphp-phpmailer/src/Exception.php';
|
||||
@require_once 'libphp-phpmailer/src/SMTP.php';
|
||||
@require_once 'libphp-phpmailer/src/PHPMailer.php';
|
||||
}
|
||||
// Alternative layout (older PHPMailer package names)
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
@require_once 'PHPMailer/src/Exception.php';
|
||||
@require_once 'PHPMailer/src/SMTP.php';
|
||||
@require_once 'PHPMailer/src/PHPMailer.php';
|
||||
}
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
@require_once 'PHPMailer/Exception.php';
|
||||
@require_once 'PHPMailer/SMTP.php';
|
||||
@require_once 'PHPMailer/PHPMailer.php';
|
||||
}
|
||||
}
|
||||
|
||||
$transport = $cfg['transport'] ?? 'smtp';
|
||||
if ($transport === 'smtp' && class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
return self::sendViaPHPMailer($cfg, $name, $email, $message, $to, $subject);
|
||||
}
|
||||
|
||||
// Fallback: attempt native mail() — works only if MTA is configured on the VM
|
||||
return self::sendViaNativeMail($cfg, $name, $email, $message, $to, $subject);
|
||||
}
|
||||
|
||||
private static function sendViaPHPMailer(array $cfg, string $name, string $email, string $body, $to, string $subject)
|
||||
{
|
||||
$mail = new PHPMailer\PHPMailer\PHPMailer(true);
|
||||
try {
|
||||
$mail->isSMTP();
|
||||
$mail->Host = $cfg['smtp_host'] ?? '';
|
||||
$mail->Port = (int)($cfg['smtp_port'] ?? 587);
|
||||
$secure = $cfg['smtp_secure'] ?? 'tls';
|
||||
if ($secure === 'ssl') $mail->SMTPSecure = PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_SMTPS;
|
||||
elseif ($secure === 'tls') $mail->SMTPSecure = PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_STARTTLS;
|
||||
else $mail->SMTPSecure = false;
|
||||
$mail->SMTPAuth = true;
|
||||
$mail->Username = $cfg['smtp_user'] ?? '';
|
||||
$mail->Password = $cfg['smtp_pass'] ?? '';
|
||||
|
||||
$fromEmail = $cfg['from_email'] ?? 'no-reply@localhost';
|
||||
$fromName = $cfg['from_name'] ?? 'App';
|
||||
$mail->setFrom($fromEmail, $fromName);
|
||||
|
||||
// Use Reply-To for the user's email to avoid spoofing From
|
||||
if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
$mail->addReplyTo($email, $name ?: $email);
|
||||
}
|
||||
if (!empty($cfg['reply_to'])) {
|
||||
$mail->addReplyTo($cfg['reply_to']);
|
||||
}
|
||||
|
||||
// Destination: prefer dynamic recipients ($to), fallback to MAIL_TO; no silent FROM fallback
|
||||
$toList = [];
|
||||
if ($to) {
|
||||
if (is_string($to)) {
|
||||
// allow comma-separated list
|
||||
$toList = array_map('trim', explode(',', $to));
|
||||
} elseif (is_array($to)) {
|
||||
$toList = $to;
|
||||
}
|
||||
} elseif (!empty(getenv('MAIL_TO'))) {
|
||||
$toList = array_map('trim', explode(',', getenv('MAIL_TO')));
|
||||
}
|
||||
$added = 0;
|
||||
foreach ($toList as $addr) {
|
||||
if (filter_var($addr, FILTER_VALIDATE_EMAIL)) {
|
||||
$mail->addAddress($addr);
|
||||
$added++;
|
||||
}
|
||||
}
|
||||
if ($added === 0) {
|
||||
return [ 'success' => false, 'error' => 'No recipients defined (set MAIL_TO or pass $to)' ];
|
||||
}
|
||||
|
||||
// DKIM (optional)
|
||||
if (!empty($cfg['dkim_domain']) && !empty($cfg['dkim_selector']) && !empty($cfg['dkim_private_key_path'])) {
|
||||
$mail->DKIM_domain = $cfg['dkim_domain'];
|
||||
$mail->DKIM_selector = $cfg['dkim_selector'];
|
||||
$mail->DKIM_private = $cfg['dkim_private_key_path'];
|
||||
}
|
||||
|
||||
$mail->isHTML(true);
|
||||
$mail->Subject = $subject;
|
||||
$safeName = htmlspecialchars($name, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
$safeEmail = htmlspecialchars($email, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
$safeBody = nl2br(htmlspecialchars($body, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'));
|
||||
$mail->Body = "<p><strong>Name:</strong> {$safeName}</p><p><strong>Email:</strong> {$safeEmail}</p><hr>{$safeBody}";
|
||||
$mail->AltBody = "Name: {$name}\nEmail: {$email}\n\n{$body}";
|
||||
|
||||
$ok = $mail->send();
|
||||
return [ 'success' => $ok ];
|
||||
} catch (\Throwable $e) {
|
||||
return [ 'success' => false, 'error' => 'PHPMailer error: ' . $e->getMessage() ];
|
||||
}
|
||||
}
|
||||
|
||||
private static function sendViaNativeMail(array $cfg, string $name, string $email, string $body, $to, string $subject)
|
||||
{
|
||||
$opts = ['reply_to' => $email];
|
||||
$html = nl2br(htmlspecialchars($body, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'));
|
||||
return self::sendMail($to, $subject, $html, $body, $opts);
|
||||
}
|
||||
}
|
||||
@ -1,76 +0,0 @@
|
||||
<?php
|
||||
// Mail configuration sourced from environment variables.
|
||||
// No secrets are stored here; the file just maps env -> config array for MailService.
|
||||
|
||||
function env_val(string $key, $default = null) {
|
||||
$v = getenv($key);
|
||||
return ($v === false || $v === null || $v === '') ? $default : $v;
|
||||
}
|
||||
|
||||
// Fallback: if critical vars are missing from process env, try to parse executor/.env
|
||||
// This helps in web/Apache contexts where .env is not exported.
|
||||
// Supports simple KEY=VALUE lines; ignores quotes and comments.
|
||||
function load_dotenv_if_needed(array $keys): void {
|
||||
$missing = array_filter($keys, fn($k) => getenv($k) === false || getenv($k) === '');
|
||||
if (empty($missing)) return;
|
||||
static $loaded = false;
|
||||
if ($loaded) return;
|
||||
$envPath = realpath(__DIR__ . '/../../.env'); // executor/.env
|
||||
if ($envPath && is_readable($envPath)) {
|
||||
$lines = @file($envPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [];
|
||||
foreach ($lines as $line) {
|
||||
if ($line[0] === '#' || trim($line) === '') continue;
|
||||
if (!str_contains($line, '=')) continue;
|
||||
[$k, $v] = array_map('trim', explode('=', $line, 2));
|
||||
// Strip potential surrounding quotes
|
||||
$v = trim($v, "\"' ");
|
||||
// Do not override existing env
|
||||
if ($k !== '' && (getenv($k) === false || getenv($k) === '')) {
|
||||
putenv("{$k}={$v}");
|
||||
}
|
||||
}
|
||||
$loaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
load_dotenv_if_needed([
|
||||
'MAIL_TRANSPORT','SMTP_HOST','SMTP_PORT','SMTP_SECURE','SMTP_USER','SMTP_PASS',
|
||||
'MAIL_FROM','MAIL_FROM_NAME','MAIL_REPLY_TO','MAIL_TO',
|
||||
'DKIM_DOMAIN','DKIM_SELECTOR','DKIM_PRIVATE_KEY_PATH'
|
||||
]);
|
||||
|
||||
$transport = env_val('MAIL_TRANSPORT', 'smtp');
|
||||
$smtp_host = env_val('SMTP_HOST');
|
||||
$smtp_port = (int) env_val('SMTP_PORT', 587);
|
||||
$smtp_secure = env_val('SMTP_SECURE', 'tls'); // tls | ssl | null
|
||||
$smtp_user = env_val('SMTP_USER');
|
||||
$smtp_pass = env_val('SMTP_PASS');
|
||||
|
||||
$from_email = env_val('MAIL_FROM', 'no-reply@localhost');
|
||||
$from_name = env_val('MAIL_FROM_NAME', 'App');
|
||||
$reply_to = env_val('MAIL_REPLY_TO');
|
||||
|
||||
$dkim_domain = env_val('DKIM_DOMAIN');
|
||||
$dkim_selector = env_val('DKIM_SELECTOR');
|
||||
$dkim_private_key_path = env_val('DKIM_PRIVATE_KEY_PATH');
|
||||
|
||||
return [
|
||||
'transport' => $transport,
|
||||
|
||||
// SMTP
|
||||
'smtp_host' => $smtp_host,
|
||||
'smtp_port' => $smtp_port,
|
||||
'smtp_secure' => $smtp_secure,
|
||||
'smtp_user' => $smtp_user,
|
||||
'smtp_pass' => $smtp_pass,
|
||||
|
||||
// From / Reply-To
|
||||
'from_email' => $from_email,
|
||||
'from_name' => $from_name,
|
||||
'reply_to' => $reply_to,
|
||||
|
||||
// DKIM (optional)
|
||||
'dkim_domain' => $dkim_domain,
|
||||
'dkim_selector' => $dkim_selector,
|
||||
'dkim_private_key_path' => $dkim_private_key_path,
|
||||
];
|
||||
140
marketplace.php
@ -1,67 +1,101 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/includes/header.php';
|
||||
// marketplace.php
|
||||
require_once 'includes/auth.php';
|
||||
require_once 'includes/header.php';
|
||||
global $pdo;
|
||||
|
||||
// Fetch All Available Cars
|
||||
// Fetch unique filter values
|
||||
try {
|
||||
$db = db();
|
||||
$cars = $db->query("SELECT c.*, b.name as branch_name
|
||||
FROM cars c
|
||||
JOIN branches b ON c.branch_id = b.id
|
||||
WHERE c.status = 'Available'
|
||||
ORDER BY c.is_featured DESC, c.created_at DESC")->fetchAll();
|
||||
$brands = $pdo->query("SELECT DISTINCT brand FROM cars ORDER BY brand")->fetchAll(PDO::FETCH_COLUMN);
|
||||
$years = $pdo->query("SELECT DISTINCT year FROM cars ORDER BY year DESC")->fetchAll(PDO::FETCH_COLUMN);
|
||||
} catch (Exception $e) {
|
||||
$brands = [];
|
||||
$years = [];
|
||||
}
|
||||
|
||||
// Build Query
|
||||
$where = ["status = 'available'"];
|
||||
$params = [];
|
||||
|
||||
if (!empty($_GET['brand'])) {
|
||||
$where[] = "brand = ?";
|
||||
$params[] = $_GET['brand'];
|
||||
}
|
||||
if (!empty($_GET['year'])) {
|
||||
$where[] = "year = ?";
|
||||
$params[] = $_GET['year'];
|
||||
}
|
||||
if (!empty($_GET['max_price'])) {
|
||||
$where[] = "price <= ?";
|
||||
$params[] = $_GET['max_price'];
|
||||
}
|
||||
|
||||
$sql = "SELECT * FROM cars WHERE " . implode(" AND ", $where) . " ORDER BY created_at DESC";
|
||||
try {
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$cars = $stmt->fetchAll();
|
||||
} catch (Exception $e) {
|
||||
$cars = [];
|
||||
}
|
||||
?>
|
||||
|
||||
<section class="container" style="padding-top: 4rem;">
|
||||
<div class="section-title">
|
||||
<div>
|
||||
<h1>Premium Marketplace</h1>
|
||||
<p>Explore our complete inventory of elite vehicles across Afghanistan.</p>
|
||||
</div>
|
||||
<div>
|
||||
<span style="background: var(--accent-color); color: #000; padding: 0.6rem 1.2rem; border-radius: 30px; font-weight: 800; font-size: 0.8rem; border: 1px solid var(--border-color);">
|
||||
<?= count($cars) ?> VEHICLES FOUND
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container mt-5">
|
||||
<h1>Marketplace</h1>
|
||||
<p class="mb-5">Browse our premium selection of vehicles.</p>
|
||||
|
||||
<!-- Filter Bar -->
|
||||
<form method="GET" class="filter-bar">
|
||||
<select name="brand">
|
||||
<option value="">All Brands</option>
|
||||
<?php foreach ($brands as $b): ?>
|
||||
<option value="<?= htmlspecialchars($b) ?>" <?= (isset($_GET['brand']) && $_GET['brand'] == $b) ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($b) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
<select name="year">
|
||||
<option value="">All Years</option>
|
||||
<?php foreach ($years as $y): ?>
|
||||
<option value="<?= htmlspecialchars($y) ?>" <?= (isset($_GET['year']) && $_GET['year'] == $y) ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($y) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
<input type="number" name="max_price" placeholder="Max Price" value="<?= htmlspecialchars($_GET['max_price'] ?? '') ?>">
|
||||
|
||||
<button type="submit" class="btn">Filter</button>
|
||||
<a href="marketplace.php" class="btn btn-outline" style="border: none; color: white;">Reset</a>
|
||||
</form>
|
||||
|
||||
<div class="grid">
|
||||
<?php foreach ($cars as $car): ?>
|
||||
<div class="car-card">
|
||||
<div class="car-image">
|
||||
<?php if ($car['is_featured']): ?>
|
||||
<span class="car-badge">FEATURED</span>
|
||||
<?php endif; ?>
|
||||
<img src="<?= htmlspecialchars($car['image_url']) ?>" alt="<?= htmlspecialchars($car['brand']) ?>">
|
||||
</div>
|
||||
<div class="car-content">
|
||||
<div class="car-title"><?= htmlspecialchars($car['year'] . ' ' . $car['brand'] . ' ' . $car['model']) ?></div>
|
||||
<div class="car-price">$<?= number_format((float)$car['price'], 0) ?></div>
|
||||
|
||||
<div class="car-meta">
|
||||
<span><i class="fas fa-cog"></i> <?= htmlspecialchars($car['transmission'] ?? 'Auto') ?></span>
|
||||
<span><i class="fas fa-tachometer-alt"></i> <?= number_format((float)$car['mileage'], 0) ?> KM</span>
|
||||
<span><i class="fas fa-gas-pump"></i> <?= htmlspecialchars($car['fuel_type'] ?? 'Gas') ?></span>
|
||||
</div>
|
||||
|
||||
<div class="car-meta">
|
||||
<span><i class="fas fa-map-marker-alt"></i> <strong><?= htmlspecialchars($car['branch_name']) ?></strong></span>
|
||||
</div>
|
||||
|
||||
<div class="installment-box">
|
||||
Installment Plan: <strong>$<?= number_format($car['price'] / 60, 0) ?>/mo</strong> for 60 months
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 1.5rem;">
|
||||
<a href="car_details.php?id=<?= $car['id'] ?>" class="btn" style="width: 100%;">View Details</a>
|
||||
<?php if (count($cars) > 0): ?>
|
||||
<?php foreach ($cars as $car): ?>
|
||||
<div class="card">
|
||||
<img src="<?= htmlspecialchars($car['image_path'] ?? $car['image_url'] ?? '') ?>" alt="<?= htmlspecialchars($car['brand']) ?>">
|
||||
<div class="card-body">
|
||||
<h3 class="card-title">
|
||||
<?= htmlspecialchars($car['brand'] . ' ' . $car['model']) ?>
|
||||
<?php if ($car['is_featured']): ?>
|
||||
<span class="badge" style="float: right;">Featured</span>
|
||||
<?php endif; ?>
|
||||
</h3>
|
||||
<div class="card-price">$<?= number_format((float)$car['price']) ?></div>
|
||||
<div class="card-meta">
|
||||
<?= $car['year'] ?> • <?= number_format((float)$car['mileage']) ?> km • <?= $car['fuel_type'] ?>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<a href="car_detail.php?id=<?= $car['id'] ?>" class="btn btn-outline" style="width:100%">View Details</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<p>No cars found matching your criteria.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
<?php require_once 'includes/footer.php'; ?>
|
||||
|
||||
140
register.php
@ -1,140 +0,0 @@
|
||||
<?php
|
||||
require_once 'includes/auth.php';
|
||||
|
||||
$error = '';
|
||||
$success = '';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$username = $_POST['username'] ?? '';
|
||||
$email = $_POST['email'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
$confirm_password = $_POST['confirm_password'] ?? '';
|
||||
|
||||
if ($password !== $confirm_password) {
|
||||
$error = "Passwords do not match";
|
||||
} else {
|
||||
$pdo = db();
|
||||
// Check if username exists
|
||||
$stmt = $pdo->prepare("SELECT id FROM users WHERE username = ? OR email = ?");
|
||||
$stmt->execute([$username, $email]);
|
||||
if ($stmt->fetch()) {
|
||||
$error = "Username or email already exists";
|
||||
} else {
|
||||
// Register user
|
||||
$hash = password_hash($password, PASSWORD_DEFAULT);
|
||||
$stmt = $pdo->prepare("INSERT INTO users (username, email, password, role) VALUES (?, ?, ?, 'Customer')");
|
||||
try {
|
||||
$stmt->execute([$username, $email, $hash]);
|
||||
$success = "Registration successful!";
|
||||
} catch (PDOException $e) {
|
||||
$error = "Registration failed: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Register - Car Market</title>
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
<style>
|
||||
body { display: flex; align-items: center; justify-content: center; min-height: 100vh; }
|
||||
.register-container {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
padding: 2.5rem;
|
||||
background: var(--card-bg);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,0.4);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
.form-group { margin-bottom: 1.2rem; }
|
||||
label { display: block; margin-bottom: 0.4rem; color: var(--text-secondary); font-size: 0.9rem; }
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 0.8rem;
|
||||
background: var(--bg-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
color: var(--text-primary);
|
||||
font-size: 1rem;
|
||||
}
|
||||
input:focus { outline: none; border-color: var(--accent-color); }
|
||||
button {
|
||||
width: 100%;
|
||||
padding: 0.8rem;
|
||||
background: var(--accent-color);
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
color: var(--bg-color);
|
||||
font-weight: 700;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
button:hover { background: var(--accent-hover); transform: translateY(-1px); }
|
||||
.error {
|
||||
background: rgba(255, 68, 68, 0.1);
|
||||
color: #ff4444;
|
||||
padding: 0.8rem;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 1.5rem;
|
||||
text-align: center;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.success {
|
||||
background: rgba(0, 200, 81, 0.1);
|
||||
color: #00C851;
|
||||
padding: 0.8rem;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 1.5rem;
|
||||
text-align: center;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
h2 { text-align: center; margin-bottom: 2rem; color: var(--accent-color); font-size: 1.8rem; }
|
||||
.links { text-align: center; margin-top: 1.5rem; font-size: 0.9rem; color: var(--text-secondary); }
|
||||
.links a { color: var(--accent-color); text-decoration: none; font-weight: 600; }
|
||||
.links a:hover { text-decoration: underline; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="register-container">
|
||||
<h2>Create Account</h2>
|
||||
<?php if ($error): ?>
|
||||
<div class="error"><?php echo htmlspecialchars($error); ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($success): ?>
|
||||
<div class="success"><?php echo htmlspecialchars($success); ?></div>
|
||||
<p style="text-align: center;"><a href="login.php" class="btn" style="color: var(--accent-color);">Proceed to Login</a></p>
|
||||
<?php else: ?>
|
||||
<form method="POST">
|
||||
<div class="form-group">
|
||||
<label>Username</label>
|
||||
<input type="text" name="username" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Email</label>
|
||||
<input type="email" name="email" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Password</label>
|
||||
<input type="password" name="password" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Confirm Password</label>
|
||||
<input type="password" name="confirm_password" required>
|
||||
</div>
|
||||
<button type="submit">Sign Up</button>
|
||||
<div class="links">
|
||||
Already have an account? <a href="login.php">Sign In</a><br>
|
||||
<a href="/index.php" style="font-size: 0.8rem; opacity: 0.7;">Back to Home</a>
|
||||
</div>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
216
setup.php
@ -1,142 +1,70 @@
|
||||
<?php
|
||||
// setup.php - Enterprise Setup Script
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
echo "<h1>AFG CARS Enterprise Setup</h1>";
|
||||
|
||||
try {
|
||||
$db = db();
|
||||
global $pdo; // Assumes db/config.php creates $pdo
|
||||
|
||||
// Drop tables if they exist for a clean re-seed
|
||||
$db->exec("SET FOREIGN_KEY_CHECKS = 0");
|
||||
$db->exec("DROP TABLE IF EXISTS notifications");
|
||||
$db->exec("DROP TABLE IF EXISTS activity_logs");
|
||||
$db->exec("DROP TABLE IF EXISTS installments");
|
||||
$db->exec("DROP TABLE IF EXISTS sales");
|
||||
$db->exec("DROP TABLE IF EXISTS reviews");
|
||||
$db->exec("DROP TABLE IF EXISTS car_images");
|
||||
$db->exec("DROP TABLE IF EXISTS cars");
|
||||
$db->exec("DROP TABLE IF EXISTS branches");
|
||||
$db->exec("DROP TABLE IF EXISTS users");
|
||||
$db->exec("SET FOREIGN_KEY_CHECKS = 1");
|
||||
echo "<h3>1. Initializing Database Schema...</h3>";
|
||||
|
||||
// Read SQL file
|
||||
$sql_file = __DIR__ . '/db/database.sql';
|
||||
if (!file_exists($sql_file)) {
|
||||
throw new Exception("Database SQL file not found at: $sql_file");
|
||||
}
|
||||
|
||||
$sql_content = file_get_contents($sql_file);
|
||||
|
||||
// Split into individual queries (basic splitting by semicolon)
|
||||
// Note: This is a simple splitter and might break on complex stored procedures, but sufficient for this schema.
|
||||
$queries = explode(';', $sql_content);
|
||||
|
||||
foreach ($queries as $query) {
|
||||
$query = trim($query);
|
||||
if (!empty($query)) {
|
||||
$pdo->exec($query);
|
||||
}
|
||||
}
|
||||
echo "<p style='color:green'>Schema imported successfully.</p>";
|
||||
|
||||
echo "<h3>2. Seeding Data...</h3>";
|
||||
|
||||
// Seed Users (Admin & Customer)
|
||||
// Check if admin exists to avoid duplicates if re-run
|
||||
$stmt = $pdo->prepare("SELECT COUNT(*) FROM users WHERE email = ?");
|
||||
$stmt->execute(['admin@afgcars.com']);
|
||||
if ($stmt->fetchColumn() == 0) {
|
||||
$password = password_hash('admin123', PASSWORD_DEFAULT);
|
||||
$stmt = $pdo->prepare("INSERT INTO users (name, email, password, role) VALUES (?, ?, ?, ?)");
|
||||
$stmt->execute(['Administrator', 'admin@afgcars.com', $password, 'admin']);
|
||||
echo "<p>Admin user created (admin@afgcars.com / admin123)</p>";
|
||||
}
|
||||
|
||||
// Create Branches table
|
||||
$db->exec("CREATE TABLE branches (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
city VARCHAR(100) NOT NULL,
|
||||
address VARCHAR(255),
|
||||
phone VARCHAR(50),
|
||||
hours VARCHAR(100)
|
||||
)");
|
||||
|
||||
// Create Users table
|
||||
$db->exec("CREATE TABLE users (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(100) UNIQUE NOT NULL,
|
||||
email VARCHAR(100) UNIQUE,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
role ENUM('Guest', 'Customer', 'Dealer', 'Employee', 'Manager', 'Admin', 'Super Admin') DEFAULT 'Customer',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)");
|
||||
|
||||
// Create Cars table
|
||||
$db->exec("CREATE TABLE cars (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
vin VARCHAR(50) UNIQUE NOT NULL,
|
||||
brand VARCHAR(100) NOT NULL,
|
||||
model VARCHAR(100) NOT NULL,
|
||||
year INT NOT NULL,
|
||||
price DECIMAL(15, 2) NOT NULL,
|
||||
mileage INT NOT NULL,
|
||||
transmission VARCHAR(50),
|
||||
fuel_type VARCHAR(50),
|
||||
status ENUM('Available', 'Reserved', 'Sold') DEFAULT 'Available',
|
||||
branch_id INT,
|
||||
dealer_id INT DEFAULT NULL,
|
||||
installment_available BOOLEAN DEFAULT 0,
|
||||
is_featured BOOLEAN DEFAULT 0,
|
||||
image_url VARCHAR(255),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (branch_id) REFERENCES branches(id),
|
||||
FOREIGN KEY (dealer_id) REFERENCES users(id)
|
||||
)");
|
||||
|
||||
// Create Car Images table
|
||||
$db->exec("CREATE TABLE car_images (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
car_id INT NOT NULL,
|
||||
image_path VARCHAR(255) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (car_id) REFERENCES cars(id) ON DELETE CASCADE
|
||||
)");
|
||||
|
||||
// Create Reviews table
|
||||
$db->exec("CREATE TABLE 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),
|
||||
comment TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (car_id) REFERENCES cars(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
)");
|
||||
|
||||
// Create Sales table
|
||||
$db->exec("CREATE TABLE sales (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
car_id INT NOT NULL,
|
||||
amount DECIMAL(15, 2) NOT NULL,
|
||||
sale_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
status ENUM('Pending', 'Completed', 'Cancelled') DEFAULT 'Pending',
|
||||
FOREIGN KEY (user_id) REFERENCES users(id),
|
||||
FOREIGN KEY (car_id) REFERENCES cars(id)
|
||||
)");
|
||||
|
||||
// Create Installments table
|
||||
$db->exec("CREATE TABLE installments (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
sale_id INT NOT NULL,
|
||||
total_amount DECIMAL(15, 2) NOT NULL,
|
||||
paid_amount DECIMAL(15, 2) DEFAULT 0,
|
||||
monthly_payment DECIMAL(15, 2) NOT NULL,
|
||||
status ENUM('Active', 'Completed', 'Overdue') DEFAULT 'Active',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (sale_id) REFERENCES sales(id) ON DELETE CASCADE
|
||||
)");
|
||||
|
||||
// Create Activity Logs table
|
||||
$db->exec("CREATE TABLE activity_logs (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT,
|
||||
action VARCHAR(255) NOT NULL,
|
||||
ip_address VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
|
||||
)");
|
||||
|
||||
// Create Notifications table
|
||||
$db->exec("CREATE TABLE notifications (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
message TEXT NOT NULL,
|
||||
is_read BOOLEAN DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
)");
|
||||
$stmt->execute(['John Doe', 'user@example.com', password_hash('user123', PASSWORD_DEFAULT), 'user']);
|
||||
echo "<p>Demo user created (user@example.com / user123)</p>";
|
||||
|
||||
// Seed Branches
|
||||
// Tables are fresh from schema import, so no need to truncate
|
||||
$branches = [
|
||||
['Kabul Main', 'Kabul', 'Shar-e-Naw, Kabul', '+93 700 111 222', '08:00 AM - 06:00 PM'],
|
||||
['Herat Branch', 'Herat', 'Main Road, Herat', '+93 700 333 444', '08:30 AM - 05:30 PM'],
|
||||
['Mazar Center', 'Mazar-i-Sharif', 'Balkh Street, Mazar', '+93 700 555 666', '08:00 AM - 05:00 PM'],
|
||||
['Kandahar Hub', 'Kandahar', 'Airport Road, Kandahar', '+93 700 777 888', '09:00 AM - 04:00 PM']
|
||||
['Kabul Main', 'Kabul', 'Shar-e-Naw, Kabul', '+93 700 111 222'],
|
||||
['Herat Branch', 'Herat', 'Main Road, Herat', '+93 700 333 444'],
|
||||
['Mazar Center', 'Mazar-i-Sharif', 'Balkh Street, Mazar', '+93 700 555 666'],
|
||||
['Kandahar Hub', 'Kandahar', 'Airport Road, Kandahar', '+93 700 777 888']
|
||||
];
|
||||
$stmt = $db->prepare("INSERT INTO branches (name, city, address, phone, hours) VALUES (?, ?, ?, ?, ?)");
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO branches (name, city, address, phone) VALUES (?, ?, ?, ?)");
|
||||
foreach ($branches as $branch) {
|
||||
$stmt->execute($branch);
|
||||
}
|
||||
echo "<p>Branches seeded.</p>";
|
||||
|
||||
// Seed Cars
|
||||
// $pdo->exec("SET FOREIGN_KEY_CHECKS=0");
|
||||
// $pdo->exec("TRUNCATE TABLE cars");
|
||||
// $pdo->exec("SET FOREIGN_KEY_CHECKS=1");
|
||||
|
||||
// Seed Cars (Exactly 20 Cars)
|
||||
$brands = ['Toyota', 'Lexus', 'Mercedes-Benz', 'BMW', 'Audi', 'Land Rover', 'Porsche', 'Tesla'];
|
||||
$models = [
|
||||
'Toyota' => ['Camry', 'Land Cruiser', 'Corolla', 'RAV4'],
|
||||
@ -149,40 +77,30 @@ try {
|
||||
'Tesla' => ['Model S', 'Model X']
|
||||
];
|
||||
|
||||
$stmt = $db->prepare("INSERT INTO cars (vin, brand, model, year, price, mileage, transmission, fuel_type, branch_id, is_featured, image_url, installment_available) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmt = $pdo->prepare("INSERT INTO cars (brand, model, year, price, mileage, fuel_type, transmission, description, image_path, branch_id, is_featured, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
|
||||
for ($i = 1; $i <= 20; $i++) {
|
||||
$brand = $brands[array_rand($brands)];
|
||||
$model = $models[$brand][array_rand($models[$brand])];
|
||||
$year = rand(2020, 2024);
|
||||
$price = rand(45000, 180000);
|
||||
$mileage = rand(0, 15000);
|
||||
$price = rand(25000, 150000);
|
||||
$mileage = rand(0, 50000);
|
||||
$fuel = rand(0, 1) ? 'Petrol' : 'Hybrid';
|
||||
$desc = "Premium condition $brand $model. Full options, well maintained.";
|
||||
$image = "assets/images/cars/car{$i}.jpg";
|
||||
$branch_id = rand(1, 4);
|
||||
$is_featured = ($i <= 8) ? 1 : 0; // 8 featured cars
|
||||
$installment_available = rand(0, 1);
|
||||
$image_url = "assets/images/cars/car{$i}.jpg";
|
||||
$vin = "VIN" . str_pad((string)$i, 10, "0", STR_PAD_LEFT);
|
||||
$is_featured = ($i <= 6) ? 1 : 0; // First 6 are featured
|
||||
|
||||
$stmt->execute([
|
||||
$vin, $brand, $model, $year, $price, $mileage,
|
||||
'Automatic', rand(0,1) ? 'Gasoline' : 'Hybrid',
|
||||
$branch_id, $is_featured, $image_url, $installment_available
|
||||
$brand, $model, $year, $price, $mileage, $fuel, 'Automatic',
|
||||
$desc, $image, $branch_id, $is_featured, 'available'
|
||||
]);
|
||||
}
|
||||
|
||||
// Seed Admin
|
||||
$stmt = $db->prepare("INSERT INTO users (username, password, role) VALUES (?, ?, ?)");
|
||||
$stmt->execute(['admin', password_hash('admin123', PASSWORD_DEFAULT), 'Super Admin']);
|
||||
|
||||
// Create flag file for automated setup
|
||||
file_put_contents(__DIR__ . '/db/setup_done.flag', date('Y-m-d H:i:s'));
|
||||
|
||||
echo "<h1>Setup Successful!</h1>";
|
||||
echo "<p>Database recreated and exactly 20 premium cars seeded.</p>";
|
||||
echo "<p><strong>Admin Credentials:</strong> admin / admin123</p>";
|
||||
echo "<a href='index.php'>Go to Home Page</a>";
|
||||
echo "<p>20 Demo cars seeded.</p>";
|
||||
|
||||
echo "<h3>Setup Complete!</h3>";
|
||||
echo "<p><a href='index.php'>Go to Homepage</a></p>";
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "<h1>Setup Failed</h1>";
|
||||
echo "<p>" . $e->getMessage() . "</p>";
|
||||
die("<h3 style='color:red'>Setup Failed: " . $e->getMessage() . "</h3>");
|
||||
}
|
||||
|
||||
99
work.php
@ -1,71 +1,46 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/includes/header.php';
|
||||
// work.php
|
||||
require_once 'includes/auth.php';
|
||||
require_once 'includes/header.php';
|
||||
?>
|
||||
|
||||
<section class="container" style="padding-top: 6rem;">
|
||||
<div class="section-title">
|
||||
<div>
|
||||
<h1>How AFG_CARS Works</h1>
|
||||
<p>A transparent, step-by-step guide to luxury vehicle ownership.</p>
|
||||
</div>
|
||||
<div class="container mt-5">
|
||||
<div class="text-center mb-5">
|
||||
<h1>How It Works</h1>
|
||||
<p>Your journey to owning a luxury car in 4 simple steps.</p>
|
||||
</div>
|
||||
|
||||
<div class="section-grid" style="margin-top: 4rem;">
|
||||
<div style="background: var(--surface-color); padding: 4rem; border-radius: var(--border-radius); border: 1px solid var(--border-color);">
|
||||
<h2 style="color: var(--accent-color); margin-bottom: 3rem; font-size: 2rem;">The Buying Process</h2>
|
||||
<div style="display: flex; flex-direction: column; gap: 3rem;">
|
||||
<div style="display: flex; gap: 2rem;">
|
||||
<div style="width: 50px; height: 50px; background: var(--accent-color); color: #000; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 900; flex-shrink: 0; font-size: 1.2rem;">1</div>
|
||||
<div>
|
||||
<h4 style="font-size: 1.2rem; margin-bottom: 0.5rem;">Online Exploration</h4>
|
||||
<p style="color: var(--text-secondary);">Browse our live inventory with detailed specifications, history reports, and high-resolution images.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; gap: 2rem;">
|
||||
<div style="width: 50px; height: 50px; background: var(--accent-color); color: #000; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 900; flex-shrink: 0; font-size: 1.2rem;">2</div>
|
||||
<div>
|
||||
<h4 style="font-size: 1.2rem; margin-bottom: 0.5rem;">Showroom Consultation</h4>
|
||||
<p style="color: var(--text-secondary);">Visit our branch to experience the vehicle in person. Our specialists provide a full technical walk-through.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; gap: 2rem;">
|
||||
<div style="width: 50px; height: 50px; background: var(--accent-color); color: #000; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 900; flex-shrink: 0; font-size: 1.2rem;">3</div>
|
||||
<div>
|
||||
<h4 style="font-size: 1.2rem; margin-bottom: 0.5rem;">Ownership & Handover</h4>
|
||||
<p style="color: var(--text-secondary);">Finalize payment or installment documents. We handle all registration and deliver your car in pristine condition.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid" style="grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); text-align: center; gap: 30px;">
|
||||
<div class="card" style="padding: 30px;">
|
||||
<div style="font-size: 3rem; color: var(--primary); font-weight: bold; margin-bottom: 20px;">1</div>
|
||||
<h3>Browse</h3>
|
||||
<p>Explore our extensive inventory of premium vehicles online or visit one of our branches.</p>
|
||||
</div>
|
||||
|
||||
<div style="background: var(--surface-color); padding: 4rem; border-radius: var(--border-radius); border: 1px solid rgba(255,255,255,0.05);">
|
||||
<h2 style="color: var(--accent-color); margin-bottom: 3rem; font-size: 2rem;">The Selling Process</h2>
|
||||
<div style="display: flex; flex-direction: column; gap: 3rem;">
|
||||
<div style="display: flex; gap: 2rem;">
|
||||
<div style="width: 50px; height: 50px; background: #fff; color: #000; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 900; flex-shrink: 0; font-size: 1.2rem;">A</div>
|
||||
<div>
|
||||
<h4 style="font-size: 1.2rem; margin-bottom: 0.5rem;">Expert Evaluation</h4>
|
||||
<p style="color: var(--text-secondary);">Submit your car details. Our team performs a comprehensive market and technical analysis to determine value.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; gap: 2rem;">
|
||||
<div style="width: 50px; height: 50px; background: #fff; color: #000; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 900; flex-shrink: 0; font-size: 1.2rem;">B</div>
|
||||
<div>
|
||||
<h4 style="font-size: 1.2rem; margin-bottom: 0.5rem;">Guaranteed Offer</h4>
|
||||
<p style="color: var(--text-secondary);">Receive a competitive buy-back or trade-in offer within 24 hours of inspection. No hidden fees.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; gap: 2rem;">
|
||||
<div style="width: 50px; height: 50px; background: #fff; color: #000; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 900; flex-shrink: 0; font-size: 1.2rem;">C</div>
|
||||
<div>
|
||||
<h4 style="font-size: 1.2rem; margin-bottom: 0.5rem;">Secure Transfer</h4>
|
||||
<p style="color: var(--text-secondary);">We handle all the legal paperwork and title transfers. Payment is released immediately upon agreement.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" style="padding: 30px;">
|
||||
<div style="font-size: 3rem; color: var(--primary); font-weight: bold; margin-bottom: 20px;">2</div>
|
||||
<h3>Select</h3>
|
||||
<p>Choose your dream car and customize your payment plan using our installment calculator.</p>
|
||||
</div>
|
||||
|
||||
<div class="card" style="padding: 30px;">
|
||||
<div style="font-size: 3rem; color: var(--primary); font-weight: bold; margin-bottom: 20px;">3</div>
|
||||
<h3>Apply</h3>
|
||||
<p>Submit a request online or in-person. Our team will process your application quickly.</p>
|
||||
</div>
|
||||
|
||||
<div class="card" style="padding: 30px;">
|
||||
<div style="font-size: 3rem; color: var(--primary); font-weight: bold; margin-bottom: 20px;">4</div>
|
||||
<h3>Drive</h3>
|
||||
<p>Once approved, sign the paperwork and drive away in your new vehicle.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="mt-5 card" style="padding: 40px; text-align: center;">
|
||||
<h2>Ready to get started?</h2>
|
||||
<p class="mb-5">Browse our marketplace to find your perfect car today.</p>
|
||||
<a href="marketplace.php" class="btn">View Inventory</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
<?php require_once 'includes/footer.php'; ?>
|
||||
|
||||