Compare commits

...

8 Commits

Author SHA1 Message Date
Flatlogic Bot
ea25e1e6ca Autosave: 20260218-083249 2026-02-18 08:32:50 +00:00
Flatlogic Bot
b2346417d7 sadiq 2026-02-17 08:45:32 +00:00
Flatlogic Bot
980e61a17b sadiq 2026-02-17 08:20:26 +00:00
Flatlogic Bot
5e1b7e7c43 sadiq 2026-02-17 06:16:03 +00:00
Flatlogic Bot
a0a862d0fb sadiq 2026-02-17 05:34:09 +00:00
Flatlogic Bot
d36e44e715 sadiq 2026-02-17 04:51:06 +00:00
Flatlogic Bot
7d4d98b000 sadiq 2026-02-16 13:09:49 +00:00
Flatlogic Bot
6b2393e243 sadiq 2026-02-16 10:17:27 +00:00
61 changed files with 2283 additions and 1012 deletions

37
README.md Normal file
View File

@ -0,0 +1,37 @@
# AFG_CARS - Supreme Automotive Marketplace
An enterprise-level car dealership management system built for professional, offline-ready deployment.
## 👥 Admin Credentials
**Access Link:** [Admin Panel](setup.php) (Initial setup & login dashboard)
- **Username:** `admin`
- **Password:** `admin123`
- **Role:** Super Admin (Full system control)
## 🏢 System Architecture (Multi-Page)
The application follows a professional Multi-Page Architecture (MPA) where each section is a standalone `.php` file for better performance and SEO:
- **Home (`index.php`):** Featured showcase, luxury hero section, testimonials, and branch locations.
- **Marketplace (`marketplace.php`):** Complete vehicle inventory with dynamic filters and installment previews.
- **Work (`work.php`):** Detailed step-by-step guides for buying and selling processes.
- **About (`about.php`):** Company history, mission, and business statistics.
- **Contact (`contact.php`):** Professional inquiry form and regional office contacts.
## 🛠 Features
- **Supreme Design:** 100% custom Dark-Themed CSS with Gold accents (No Bootstrap/CDN).
- **Offline Ready:** All assets (images, fonts, scripts) are stored locally for XAMPP deployment.
- **Multi-Branch:** Integrated data management for Kabul, Herat, Mazar, and Kandahar.
- **Installment System:** Automated calculation logic for monthly payments.
- **Enterprise RBAC:** Prepared structure for Guests, Dealers, and Admins.
## 🚀 Installation
1. Place the project folder inside your `htdocs` directory.
2. Ensure MySQL/MariaDB is running.
3. Open `http://localhost/your-folder-name/setup.php` in your browser.
4. Click **"Run Automated Setup"** to initialize the database and seed 20 premium cars.
---
**Developed by:** Mohammad Sadiq
**Project Type:** University Final-Year Enterprise System
**Environment:** PHP 8.x + MariaDB (100% Offline)

58
about.php Normal file
View File

@ -0,0 +1,58 @@
<?php
// about.php
require_once 'includes/auth.php';
require_once 'includes/header.php';
?>
<div class="container mt-5">
<div class="grid" style="grid-template-columns: 1fr 1fr; gap: 50px; align-items: center;">
<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>
<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>
</div>
</div>
<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 'includes/footer.php'; ?>

101
admin/branches.php Normal file
View File

@ -0,0 +1,101 @@
<?php
require_once 'includes/header.php';
$pdo = db();
// Handle Form Submission
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = $_POST['name'];
$location = $_POST['location'];
$phone = $_POST['phone'];
$email = $_POST['email'];
$stmt = $pdo->prepare("INSERT INTO branches (name, location, phone, email) VALUES (?, ?, ?, ?)");
$stmt->execute([$name, $location, $phone, $email]);
// Log activity
$adminId = $_SESSION['user_id'];
$pdo->prepare("INSERT INTO activity_logs (user_id, action) VALUES (?, 'Added new branch: $name')")->execute([$adminId]);
echo "<div style='padding: 1rem; background: #4caf50; color: white; margin-bottom: 1rem;'>Branch Added Successfully</div>";
}
// Delete Branch
if (isset($_GET['delete'])) {
$id = $_GET['delete'];
$pdo->prepare("DELETE FROM branches WHERE id = ?")->execute([$id]);
echo "<div style='padding: 1rem; background: #f44336; color: white; margin-bottom: 1rem;'>Branch Deleted</div>";
}
$branches = $pdo->query("SELECT * FROM branches ORDER BY created_at DESC")->fetchAll();
?>
<div class="page-header">
<h1>Branch Management</h1>
<button onclick="document.getElementById('addBranchModal').style.display='block'" class="btn-sm btn-primary">Add New Branch</button>
</div>
<div class="card-grid" style="grid-template-columns: 1fr;">
<table class="data-table">
<thead>
<tr>
<th>Name</th>
<th>Location</th>
<th>Contact</th>
<th>Created At</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($branches as $branch): ?>
<tr>
<td><?php echo htmlspecialchars($branch['name']); ?></td>
<td><?php echo htmlspecialchars($branch['location']); ?></td>
<td>
<?php echo htmlspecialchars($branch['phone']); ?><br>
<small><?php echo htmlspecialchars($branch['email']); ?></small>
</td>
<td><?php echo $branch['created_at']; ?></td>
<td>
<a href="?delete=<?php echo $branch['id']; ?>" class="btn-sm btn-danger" onclick="return confirm('Delete this branch?')">Delete</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<!-- Simple Modal for Adding Branch -->
<div id="addBranchModal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.8); z-index:1000;">
<div style="background:var(--card-bg); width:400px; margin: 100px auto; padding:2rem; border-radius:8px; border:1px solid var(--border-color);">
<h2 style="margin-bottom:1rem; color:var(--text-primary);">Add Branch</h2>
<form method="POST">
<div style="margin-bottom:1rem;">
<label style="display:block; color:var(--text-secondary); margin-bottom:0.5rem;">Branch Name</label>
<input type="text" name="name" required style="width:100%; padding:0.8rem; background:var(--bg-color); border:1px solid var(--border-color); color:var(--text-primary);">
</div>
<div style="margin-bottom:1rem;">
<label style="display:block; color:var(--text-secondary); margin-bottom:0.5rem;">Location</label>
<input type="text" name="location" required style="width:100%; padding:0.8rem; background:var(--bg-color); border:1px solid var(--border-color); color:var(--text-primary);">
</div>
<div style="margin-bottom:1rem;">
<label style="display:block; color:var(--text-secondary); margin-bottom:0.5rem;">Phone</label>
<input type="text" name="phone" style="width:100%; padding:0.8rem; background:var(--bg-color); border:1px solid var(--border-color); color:var(--text-primary);">
</div>
<div style="margin-bottom:1rem;">
<label style="display:block; color:var(--text-secondary); margin-bottom:0.5rem;">Email</label>
<input type="email" name="email" style="width:100%; padding:0.8rem; background:var(--bg-color); border:1px solid var(--border-color); color:var(--text-primary);">
</div>
<button type="submit" class="btn-sm btn-primary" style="width:100%;">Create Branch</button>
<button type="button" onclick="document.getElementById('addBranchModal').style.display='none'" class="btn-sm" style="width:100%; margin-top:0.5rem; background:transparent; border:1px solid var(--border-color);">Cancel</button>
</form>
</div>
</div>
<script>
// Auto-open modal if action=add
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('action') === 'add') {
document.getElementById('addBranchModal').style.display = 'block';
}
</script>
<?php require_once 'includes/footer.php'; ?>

106
admin/cars.php Normal file
View File

@ -0,0 +1,106 @@
<?php
// admin/cars.php
require_once '../includes/auth.php';
require_once '../includes/middleware.php';
requireAdmin();
require_once '../includes/header.php';
global $pdo;
$msg = '';
// Handle Delete
if (isset($_GET['delete'])) {
$stmt = $pdo->prepare("DELETE FROM cars WHERE id = ?");
$stmt->execute([$_GET['delete']]);
$msg = "Car deleted successfully.";
}
// 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();
?>
<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; ?>
<!-- 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 $b): ?>
<option value="<?= $b['id'] ?>"><?= htmlspecialchars($b['city']) ?></option>
<?php endforeach; ?>
</select>
<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>
<?php require_once '../includes/footer.php'; ?>

78
admin/dashboard.php Normal file
View 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'; ?>

45
admin/dealers.php Normal file
View File

@ -0,0 +1,45 @@
<?php
require_once 'includes/header.php';
$pdo = db();
$dealers = $pdo->query("SELECT * FROM users WHERE role = 'Dealer' ORDER BY created_at DESC")->fetchAll();
?>
<div class="page-header">
<h1>Dealer Management</h1>
<a href="users.php?role=Dealer" class="btn-sm btn-primary">Manage All Users</a>
</div>
<div class="card-grid" style="grid-template-columns: 1fr;">
<table class="data-table">
<thead>
<tr>
<th>Username</th>
<th>Email</th>
<th>Status</th>
<th>Performance (Sales)</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($dealers as $dealer): ?>
<tr>
<td><?php echo htmlspecialchars($dealer['username']); ?></td>
<td><?php echo htmlspecialchars($dealer['email']); ?></td>
<td><span style="padding:0.2rem 0.5rem; background:rgba(76, 175, 80, 0.2); color:#4caf50; border-radius:4px;">Active</span></td>
<td>$0.00 (0 Sales)</td> <!-- Placeholder for now -->
<td>
<a href="users.php?edit=<?php echo $dealer['id']; ?>" class="btn-sm" style="background:var(--accent-color); color:black;">Edit</a>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($dealers)): ?>
<tr>
<td colspan="5" style="text-align:center; color:var(--text-secondary); padding:2rem;">No dealers found. Create one in the Users section.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
<?php require_once 'includes/footer.php'; ?>

0
admin/employees.php Normal file
View File

View File

@ -0,0 +1,4 @@
</main>
</div>
</body>
</html>

123
admin/includes/header.php Normal file
View File

@ -0,0 +1,123 @@
<?php
require_once __DIR__ . '/../../includes/auth.php';
requireAdmin();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Dashboard</title>
<link rel="stylesheet" href="/assets/css/style.css">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
.admin-layout { display: flex; min-height: 100vh; }
.sidebar {
width: 250px;
background: var(--surface-color);
border-right: 1px solid var(--border-color);
padding: 2rem 1rem;
display: flex;
flex-direction: column;
position: fixed;
height: 100vh;
overflow-y: auto;
}
.main-content {
flex: 1;
margin-left: 250px;
padding: 2rem;
background: var(--bg-color);
}
.sidebar-brand {
color: var(--accent-color);
font-size: 1.5rem;
font-weight: 800;
margin-bottom: 2rem;
text-align: center;
letter-spacing: 1px;
text-decoration: none;
}
.nav-link {
display: block;
padding: 0.8rem 1rem;
color: var(--text-secondary);
text-decoration: none;
border-radius: 6px;
margin-bottom: 0.5rem;
transition: all 0.2s;
}
.nav-link:hover, .nav-link.active {
background: rgba(212, 175, 55, 0.1);
color: var(--accent-color);
}
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
.stat-card {
background: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: var(--border-radius);
padding: 1.5rem;
}
.stat-value { font-size: 2rem; font-weight: 700; color: var(--text-primary); margin-bottom: 0.5rem; }
.stat-label { color: var(--text-secondary); font-size: 0.9rem; }
.data-table {
width: 100%;
border-collapse: collapse;
background: var(--card-bg);
border-radius: var(--border-radius);
overflow: hidden;
border: 1px solid var(--border-color);
}
.data-table th, .data-table td {
padding: 1rem;
text-align: left;
border-bottom: 1px solid var(--border-color);
}
.data-table th {
background: rgba(255,255,255,0.05);
color: var(--accent-color);
font-weight: 600;
}
.data-table tr:hover { background: rgba(255,255,255,0.02); }
.btn-sm {
padding: 0.4rem 0.8rem;
font-size: 0.8rem;
border-radius: 4px;
text-decoration: none;
display: inline-block;
cursor: pointer;
border: none;
}
.btn-danger { background: rgba(255, 68, 68, 0.2); color: #ff4444; }
.btn-danger:hover { background: rgba(255, 68, 68, 0.3); }
.btn-primary { background: var(--accent-color); color: var(--bg-color); font-weight: 600; }
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
}
h1 { font-size: 1.8rem; color: var(--text-primary); }
</style>
</head>
<body>
<div class="admin-layout">
<aside class="sidebar">
<a href="/admin/dashboard.php" class="sidebar-brand">AFG_CARS ADMIN</a>
<nav>
<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>
<a href="/admin/cars.php" class="nav-link <?php echo basename($_SERVER['PHP_SELF']) == 'cars.php' ? 'active' : ''; ?>">Cars Inventory</a>
<a href="/admin/sales.php" class="nav-link <?php echo basename($_SERVER['PHP_SELF']) == 'sales.php' ? 'active' : ''; ?>">Sales & Installments</a>
<a href="/admin/reports.php" class="nav-link <?php echo basename($_SERVER['PHP_SELF']) == 'reports.php' ? 'active' : ''; ?>">Reports</a>
<a href="/logout.php" class="nav-link" style="margin-top: auto; color: #ff4444;">Logout</a>
</nav>
</aside>
<main class="main-content">

1
admin/index.php Normal file
View File

@ -0,0 +1 @@
<?php header('Location: dashboard.php'); ?>

0
admin/installments.php Normal file
View File

12
admin/reports.php Normal file
View File

@ -0,0 +1,12 @@
<?php
require_once 'includes/header.php';
?>
<div class="page-header">
<h1>Reports & Analytics</h1>
</div>
<div style="text-align: center; padding: 4rem; background: var(--card-bg); border: 1px solid var(--border-color); border-radius: var(--border-radius);">
<i class="fas fa-chart-pie" style="font-size: 3rem; color: var(--accent-color); margin-bottom: 1rem;"></i>
<h3>Enterprise Reporting</h3>
<p style="color: var(--text-secondary);">Financial reports, dealer performance, and inventory turnover analytics will be available here.</p>
</div>
<?php require_once 'includes/footer.php'; ?>

12
admin/sales.php Normal file
View File

@ -0,0 +1,12 @@
<?php
require_once 'includes/header.php';
?>
<div class="page-header">
<h1>Sales & Installments</h1>
</div>
<div style="text-align: center; padding: 4rem; background: var(--card-bg); border: 1px solid var(--border-color); border-radius: var(--border-radius);">
<i class="fas fa-file-invoice-dollar" style="font-size: 3rem; color: var(--accent-color); margin-bottom: 1rem;"></i>
<h3>Sales Records System</h3>
<p style="color: var(--text-secondary);">No sales recorded yet. Once sales are made, they will appear here along with installment tracking.</p>
</div>
<?php require_once 'includes/footer.php'; ?>

0
admin/settings.php Normal file
View File

145
admin/users.php Normal file
View File

@ -0,0 +1,145 @@
<?php
require_once 'includes/header.php';
$pdo = db();
if (isset($_POST['delete_user'])) {
$id = $_POST['user_id'];
if ($id != $_SESSION['user_id']) {
$stmt = $pdo->prepare("DELETE FROM users WHERE id = ?");
$stmt->execute([$id]);
echo "<div style='padding: 1rem; background: #f44336; color: white;'>User Deleted</div>";
}
}
// Handle Add User
if (isset($_POST['add_user'])) {
$username = $_POST['username'];
$email = $_POST['email'];
$password = password_hash($_POST['password'], PASSWORD_DEFAULT);
$role = $_POST['role'];
// Check if exists
$stmt = $pdo->prepare("SELECT id FROM users WHERE username = ? OR email = ?");
$stmt->execute([$username, $email]);
if ($stmt->fetch()) {
echo "<div style='padding: 1rem; background: #f44336; color: white;'>User already exists</div>";
} else {
$stmt = $pdo->prepare("INSERT INTO users (username, email, password, role) VALUES (?, ?, ?, ?)");
$stmt->execute([$username, $email, $password, $role]);
echo "<div style='padding: 1rem; background: #4caf50; color: white;'>User Added Successfully</div>";
}
}
if (isset($_POST['update_role'])) {
$id = $_POST['user_id'];
$role = $_POST['role'];
if ($id != $_SESSION['user_id']) { // Prevent changing own role to something lower accidentally
$stmt = $pdo->prepare("UPDATE users SET role = ? WHERE id = ?");
$stmt->execute([$role, $id]);
echo "<div style='padding: 1rem; background: #4caf50; color: white;'>User Role Updated</div>";
}
}
$stmt = $pdo->query("SELECT * FROM users ORDER BY created_at DESC");
$users = $stmt->fetchAll();
?>
<div class="page-header">
<h1>User Management</h1>
<button onclick="document.getElementById('addUserModal').style.display='block'" class="btn-sm btn-primary">Add New User</button>
</div>
<div style="overflow-x: auto;">
<table class="data-table">
<thead>
<tr>
<th>ID</th>
<th>Username</th>
<th>Role</th>
<th>Joined</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($users as $user): ?>
<tr>
<td>#<?php echo $user['id']; ?></td>
<td>
<?php echo htmlspecialchars($user['username']); ?><br>
<small style="color:var(--text-secondary);"><?php echo htmlspecialchars($user['email'] ?? ''); ?></small>
</td>
<td>
<?php if ($user['id'] == $_SESSION['user_id']): ?>
<span class="badge badge-admin"><?php echo htmlspecialchars($user['role']); ?></span>
<?php else: ?>
<form method="POST" style="display:inline;">
<input type="hidden" name="user_id" value="<?php echo $user['id']; ?>">
<select name="role" onchange="this.form.submit()" style="padding:0.2rem; border-radius:4px; background:var(--bg-color); color:var(--text-primary); border:1px solid var(--border-color);">
<?php
$roles = ['Guest','Customer','Dealer','Employee','Manager','Admin','Super Admin'];
foreach ($roles as $r) {
$selected = ($user['role'] === $r) ? 'selected' : '';
echo "<option value='$r' $selected>$r</option>";
}
?>
</select>
<input type="hidden" name="update_role" value="1">
</form>
<?php endif; ?>
</td>
<td><?php echo date('M j, Y', strtotime($user['created_at'])); ?></td>
<td>
<?php if ($user['id'] != $_SESSION['user_id']): ?>
<form method="POST" onsubmit="return confirm('Are you sure?');" style="display: inline;">
<input type="hidden" name="user_id" value="<?php echo $user['id']; ?>">
<button type="submit" name="delete_user" class="btn-sm btn-danger">Delete</button>
</form>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php require_once 'includes/footer.php'; ?>
<!-- Add User Modal -->
<div id="addUserModal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.8); z-index:1000;">
<div style="background:var(--card-bg); width:400px; margin: 100px auto; padding:2rem; border-radius:8px; border:1px solid var(--border-color);">
<h2 style="margin-bottom:1rem; color:var(--text-primary);">Add User</h2>
<form method="POST">
<div style="margin-bottom:1rem;">
<label style="display:block; color:var(--text-secondary); margin-bottom:0.5rem;">Username</label>
<input type="text" name="username" required style="width:100%; padding:0.8rem; background:var(--bg-color); border:1px solid var(--border-color); color:var(--text-primary);">
</div>
<div style="margin-bottom:1rem;">
<label style="display:block; color:var(--text-secondary); margin-bottom:0.5rem;">Email</label>
<input type="email" name="email" required style="width:100%; padding:0.8rem; background:var(--bg-color); border:1px solid var(--border-color); color:var(--text-primary);">
</div>
<div style="margin-bottom:1rem;">
<label style="display:block; color:var(--text-secondary); margin-bottom:0.5rem;">Password</label>
<input type="password" name="password" required style="width:100%; padding:0.8rem; background:var(--bg-color); border:1px solid var(--border-color); color:var(--text-primary);">
</div>
<div style="margin-bottom:1rem;">
<label style="display:block; color:var(--text-secondary); margin-bottom:0.5rem;">Role</label>
<select name="role" style="width:100%; padding:0.8rem; background:var(--bg-color); border:1px solid var(--border-color); color:var(--text-primary);">
<option value="Customer">Customer</option>
<option value="Dealer">Dealer</option>
<option value="Manager">Manager</option>
<option value="Admin">Admin</option>
</select>
</div>
<button type="submit" name="add_user" class="btn-sm btn-primary" style="width:100%;">Create User</button>
<button type="button" onclick="document.getElementById('addUserModal').style.display='none'" class="btn-sm" style="width:100%; margin-top:0.5rem; background:transparent; border:1px solid var(--border-color);">Cancel</button>
</form>
</div>
</div>
<script>
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('action') === 'add') {
document.getElementById('addUserModal').style.display = 'block';
}
</script>

View 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');
}

View File

@ -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,
];

228
assets/css/style.css Normal file
View File

@ -0,0 +1,228 @@
/* AFG CARS Enterprise Style - 100% Offline */
:root {
--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;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background-color: var(--dark);
color: var(--light);
line-height: 1.6;
min-height: 100vh;
display: flex;
flex-direction: column;
}
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.5rem; font-weight: bold; color: var(--primary); }
.nav-links { display: flex; gap: 20px; }
.nav-links a:hover { color: var(--primary); }
/* Hero */
.hero {
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;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.hero h1 { font-size: 3rem; margin-bottom: 20px; }
.hero p { font-size: 1.2rem; margin-bottom: 30px; opacity: 0.9; }
/* 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;
}
/* 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;
}
/* Admin Dashboard */
.dashboard-container {
display: flex;
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); }
/* 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: rgba(0,0,0,0.5);
padding: 40px 0;
margin-top: auto;
border-top: 1px solid var(--glass-border);
text-align: center;
}
/* Helpers */
.text-center { text-align: center; }
.mt-5 { margin-top: 3rem; }
.mb-5 { margin-bottom: 3rem; }
.text-danger { color: #e63946; }
.text-success { color: #2a9d8f; }
/* 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;
}
.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;
}

BIN
assets/images/cars/car1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
assets/images/cars/car2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
assets/images/cars/car3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
assets/images/cars/car4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
assets/images/cars/car5.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
assets/images/cars/car6.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
assets/images/cars/car7.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
assets/images/cars/car8.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
assets/images/cars/car9.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
assets/images/hero.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
assets/images/hero/bg.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

118
car_detail.php Normal file
View 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;">&larr; 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'; ?>

86
contact.php Normal file
View File

@ -0,0 +1,86 @@
<?php
// contact.php
require_once 'includes/auth.php';
require_once 'includes/header.php';
global $pdo;
$msg = '';
$inquiry_car = null;
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.";
}
?>
<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>
<?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" name="email" class="form-control" required>
</div>
<div class="form-group">
<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>
<button type="submit" class="btn">Send Message</button>
</form>
</div>
<div>
<h3>Head Office</h3>
<p class="mb-5">Shar-e-Naw, Kabul, Afghanistan</p>
<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>
</div>
<?php require_once 'includes/footer.php'; ?>

View File

@ -1,17 +1,22 @@
<?php <?php
// Generated by setup_mariadb_project.sh — edit as needed. // db/config.php
// Database Configuration
define('DB_HOST', '127.0.0.1'); define('DB_HOST', '127.0.0.1');
define('DB_NAME', 'app_38474'); define('DB_NAME', 'app_38474');
define('DB_USER', 'app_38474'); define('DB_USER', 'app_38474');
define('DB_PASS', '31621ed0-58d1-46b5-b7d0-9eb1e1abacf7'); define('DB_PASS', '31621ed0-58d1-46b5-b7d0-9eb1e1abacf7');
function db() { try {
static $pdo; $pdo = new PDO("mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4", DB_USER, DB_PASS);
if (!$pdo) { $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [ $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, } catch (PDOException $e) {
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, die("Database Connection Failed: " . $e->getMessage());
]);
} }
// Global helper function if needed, but we can just use $pdo
function getDB() {
global $pdo;
return $pdo; return $pdo;
} }

86
db/database.sql Normal file
View 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;

148
db/database_dump.sql Normal file
View File

@ -0,0 +1,148 @@
/*M!999999\- enable the sandbox mode */
-- MariaDB dump 10.19 Distrib 10.11.14-MariaDB, for debian-linux-gnu (x86_64)
--
-- Host: 127.0.0.1 Database: app_38474
-- ------------------------------------------------------
-- Server version 10.11.14-MariaDB-0+deb12u2
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Table structure for table `branches`
--
DROP TABLE IF EXISTS `branches`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `branches` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`city` varchar(100) NOT NULL,
`address` varchar(255) DEFAULT NULL,
`phone` varchar(50) DEFAULT NULL,
`hours` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `branches`
--
LOCK TABLES `branches` WRITE;
/*!40000 ALTER TABLE `branches` DISABLE KEYS */;
INSERT INTO `branches` VALUES
(1,'Kabul Main','Kabul','Shar-e-Naw, Kabul','+93 700 111 222','08:00 AM - 06:00 PM'),
(2,'Herat Branch','Herat','Main Road, Herat','+93 700 333 444','08:30 AM - 05:30 PM'),
(3,'Mazar Center','Mazar-i-Sharif','Balkh Street, Mazar','+93 700 555 666','08:00 AM - 05:00 PM'),
(4,'Kandahar Hub','Kandahar','Airport Road, Kandahar','+93 700 777 888','09:00 AM - 04:00 PM');
/*!40000 ALTER TABLE `branches` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `cars`
--
DROP TABLE IF EXISTS `cars`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `cars` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`vin` varchar(50) NOT NULL,
`brand` varchar(100) NOT NULL,
`model` varchar(100) NOT NULL,
`year` int(11) NOT NULL,
`price` decimal(15,2) NOT NULL,
`mileage` int(11) NOT NULL,
`transmission` varchar(50) DEFAULT NULL,
`fuel_type` varchar(50) DEFAULT NULL,
`status` enum('Available','Reserved','Sold') DEFAULT 'Available',
`branch_id` int(11) DEFAULT NULL,
`is_featured` tinyint(1) DEFAULT 0,
`image_url` varchar(255) DEFAULT NULL,
`created_at` timestamp NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `vin` (`vin`),
KEY `branch_id` (`branch_id`),
CONSTRAINT `cars_ibfk_1` FOREIGN KEY (`branch_id`) REFERENCES `branches` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `cars`
--
LOCK TABLES `cars` WRITE;
/*!40000 ALTER TABLE `cars` DISABLE KEYS */;
INSERT INTO `cars` VALUES
(1,'VIN0000000001','Tesla','Model X',2023,66020.00,8568,'Automatic','Hybrid','Available',4,1,'assets/images/cars/car1.jpg','2026-02-16 17:44:23'),
(2,'VIN0000000002','BMW','X7',2020,114269.00,8462,'Automatic','Hybrid','Available',4,1,'assets/images/cars/car2.jpg','2026-02-16 17:44:23'),
(3,'VIN0000000003','Mercedes-Benz','E-Class',2022,176551.00,14978,'Automatic','Gasoline','Available',1,1,'assets/images/cars/car3.jpg','2026-02-16 17:44:23'),
(4,'VIN0000000004','Tesla','Model S',2021,129987.00,5263,'Automatic','Gasoline','Available',1,1,'assets/images/cars/car4.jpg','2026-02-16 17:44:23'),
(5,'VIN0000000005','BMW','X7',2020,71914.00,2277,'Automatic','Hybrid','Available',4,1,'assets/images/cars/car5.jpg','2026-02-16 17:44:23'),
(6,'VIN0000000006','Land Rover','Defender',2024,53857.00,13809,'Automatic','Hybrid','Available',2,1,'assets/images/cars/car6.jpg','2026-02-16 17:44:23'),
(7,'VIN0000000007','Lexus','RX 350',2020,50362.00,8212,'Automatic','Gasoline','Available',3,1,'assets/images/cars/car7.jpg','2026-02-16 17:44:23'),
(8,'VIN0000000008','Lexus','LX 600',2020,157026.00,192,'Automatic','Hybrid','Available',1,1,'assets/images/cars/car8.jpg','2026-02-16 17:44:23'),
(9,'VIN0000000009','Lexus','ES 350',2022,149865.00,8073,'Automatic','Hybrid','Available',4,0,'assets/images/cars/car9.jpg','2026-02-16 17:44:23'),
(10,'VIN0000000010','Lexus','LX 600',2022,136955.00,14064,'Automatic','Hybrid','Available',2,0,'assets/images/cars/car10.jpg','2026-02-16 17:44:23'),
(11,'VIN0000000011','Toyota','Land Cruiser',2021,116083.00,12545,'Automatic','Gasoline','Available',1,0,'assets/images/cars/car11.jpg','2026-02-16 17:44:23'),
(12,'VIN0000000012','Mercedes-Benz','S-Class',2023,177233.00,2170,'Automatic','Hybrid','Available',2,0,'assets/images/cars/car12.jpg','2026-02-16 17:44:23'),
(13,'VIN0000000013','BMW','X7',2022,176162.00,2440,'Automatic','Hybrid','Available',3,0,'assets/images/cars/car13.jpg','2026-02-16 17:44:23'),
(14,'VIN0000000014','Porsche','Cayenne',2020,113840.00,13614,'Automatic','Gasoline','Available',4,0,'assets/images/cars/car14.jpg','2026-02-16 17:44:23'),
(15,'VIN0000000015','Tesla','Model S',2021,53508.00,10674,'Automatic','Gasoline','Available',4,0,'assets/images/cars/car15.jpg','2026-02-16 17:44:23'),
(16,'VIN0000000016','BMW','7 Series',2020,108898.00,5346,'Automatic','Hybrid','Available',4,0,'assets/images/cars/car16.jpg','2026-02-16 17:44:23'),
(17,'VIN0000000017','Mercedes-Benz','S-Class',2020,56037.00,12766,'Automatic','Hybrid','Available',3,0,'assets/images/cars/car17.jpg','2026-02-16 17:44:23'),
(18,'VIN0000000018','Toyota','RAV4',2024,150289.00,8590,'Automatic','Hybrid','Available',1,0,'assets/images/cars/car18.jpg','2026-02-16 17:44:23'),
(19,'VIN0000000019','Land Rover','Defender',2021,73639.00,1603,'Automatic','Hybrid','Available',2,0,'assets/images/cars/car19.jpg','2026-02-16 17:44:23'),
(20,'VIN0000000020','Toyota','Camry',2021,76785.00,13561,'Automatic','Hybrid','Available',3,0,'assets/images/cars/car20.jpg','2026-02-16 17:44:23');
/*!40000 ALTER TABLE `cars` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `users`
--
DROP TABLE IF EXISTS `users`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(100) NOT NULL,
`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 AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `users`
--
LOCK TABLES `users` WRITE;
/*!40000 ALTER TABLE `users` DISABLE KEYS */;
INSERT INTO `users` VALUES
(1,'admin','$2y$10$QCCJTMtWMNo4VDc5azbJ2evY2xFdNkuJKrJPgrzWMp55mH6bTv1we','Super Admin','2026-02-16 17:44:23');
/*!40000 ALTER TABLE `users` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2026-02-17 5:33:17

View File

@ -0,0 +1,72 @@
-- Branches Table
CREATE TABLE IF NOT EXISTS `branches` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(100) NOT NULL,
`location` VARCHAR(255) NOT NULL,
`phone` VARCHAR(20),
`email` VARCHAR(100),
`manager_id` INT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Car Images Table (Multiple Images)
CREATE TABLE IF NOT EXISTS `car_images` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`car_id` INT NOT NULL,
`image_url` VARCHAR(255) NOT NULL,
`is_primary` TINYINT(1) DEFAULT 0,
FOREIGN KEY (`car_id`) REFERENCES `cars`(`id`) ON DELETE CASCADE
);
-- Sales Table
CREATE TABLE IF NOT EXISTS `sales` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`car_id` INT NOT NULL,
`buyer_id` INT NOT NULL,
`seller_id` INT, -- Dealer or Admin who sold it
`sale_date` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`final_price` DECIMAL(15, 2) NOT NULL,
`payment_method` ENUM('Cash', 'Installment') DEFAULT 'Cash',
`status` ENUM('Pending', 'Completed', 'Cancelled') DEFAULT 'Pending',
FOREIGN KEY (`car_id`) REFERENCES `cars`(`id`),
FOREIGN KEY (`buyer_id`) REFERENCES `users`(`id`)
);
-- Installments Table
CREATE TABLE IF NOT EXISTS `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.00,
`monthly_payment` DECIMAL(15, 2) NOT NULL,
`due_date` DATE,
`status` ENUM('Active', 'Completed', 'Defaulted') DEFAULT 'Active',
FOREIGN KEY (`sale_id`) REFERENCES `sales`(`id`) ON DELETE CASCADE
);
-- Activity Logs
CREATE TABLE IF NOT EXISTS `activity_logs` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`user_id` INT,
`action` VARCHAR(255) NOT NULL,
`details` TEXT,
`ip_address` VARCHAR(45),
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Notifications
CREATE TABLE IF NOT EXISTS `notifications` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`user_id` INT NOT NULL,
`message` TEXT NOT NULL,
`is_read` TINYINT(1) DEFAULT 0,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE
);
-- Update Cars Table
ALTER TABLE `cars` ADD COLUMN IF NOT EXISTS `dealer_id` INT DEFAULT NULL;
ALTER TABLE `cars` ADD COLUMN IF NOT EXISTS `installment_available` TINYINT(1) DEFAULT 0;
-- Update Users Table (Ensure role column is correct - strictly speaking it already exists but this is safe)
-- ALTER TABLE `users` MODIFY COLUMN `role` ENUM('Guest','Customer','Dealer','Employee','Manager','Admin','Super Admin') DEFAULT 'Customer';

1
db/setup_done.flag Normal file
View File

@ -0,0 +1 @@
2026-02-17 08:38:35

46
includes/auth.php Normal file
View File

@ -0,0 +1,46 @@
<?php
// 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']);
}
// Get current user role
function getUserRole() {
return $_SESSION['user_role'] ?? 'guest';
}
// Get current user name
function getUserName() {
return $_SESSION['user_name'] ?? 'Guest';
}
// 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['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");
exit;
}

37
includes/footer.php Normal file
View File

@ -0,0 +1,37 @@
<?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>&copy; <?= date('Y') ?> AFG CARS - Elite Dealership System.</p>
<p style="font-size: 0.8rem; margin-top: 5px;">100% Offline | Enterprise Ready</p>
</div>
</div>
</footer>
</body>
</html>

40
includes/header.php Normal file
View File

@ -0,0 +1,40 @@
<?php
// 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>AFG CARS Enterprise</title>
<link rel="stylesheet" href="/assets/css/style.css">
</head>
<body>
<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>

189
includes/init_db.php Normal file
View File

@ -0,0 +1,189 @@
<?php
require_once __DIR__ . '/../db/config.php';
function ensure_db_setup() {
$flagFile = __DIR__ . '/../db/setup_done.flag';
if (file_exists($flagFile)) {
return;
}
try {
$db = db();
// Schema Creation (IF NOT EXISTS to be safe)
$db->exec("SET FOREIGN_KEY_CHECKS = 0");
// Branches
$db->exec("CREATE TABLE IF NOT EXISTS 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)
)");
// Users
$db->exec("CREATE TABLE IF NOT EXISTS 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
)");
// Cars
$db->exec("CREATE TABLE IF NOT EXISTS 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)
)");
// Car Images
$db->exec("CREATE TABLE IF NOT EXISTS 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
)");
// Reviews
$db->exec("CREATE TABLE IF NOT EXISTS reviews (
id INT AUTO_INCREMENT PRIMARY KEY,
car_id INT NOT NULL,
user_id INT NOT NULL,
rating INT NOT NULL CHECK (rating >= 1 AND rating <= 5),
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
)");
// Sales
$db->exec("CREATE TABLE IF NOT EXISTS 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)
)");
// Installments
$db->exec("CREATE TABLE IF NOT EXISTS 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
)");
// Activity Logs
$db->exec("CREATE TABLE IF NOT EXISTS 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
)");
// Notifications
$db->exec("CREATE TABLE IF NOT EXISTS 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
)");
$db->exec("SET FOREIGN_KEY_CHECKS = 1");
// Seeding (Only if empty)
$stmt = $db->query("SELECT COUNT(*) FROM branches");
if ($stmt->fetchColumn() == 0) {
$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']
];
$stmt = $db->prepare("INSERT INTO branches (name, city, address, phone, hours) VALUES (?, ?, ?, ?, ?)");
foreach ($branches as $branch) {
$stmt->execute($branch);
}
}
$stmt = $db->query("SELECT COUNT(*) FROM cars");
if ($stmt->fetchColumn() == 0) {
$brands = ['Toyota', 'Lexus', 'Mercedes-Benz', 'BMW', 'Audi', 'Land Rover', 'Porsche', 'Tesla'];
$models = [
'Toyota' => ['Camry', 'Land Cruiser', 'Corolla', 'RAV4'],
'Lexus' => ['LX 600', 'RX 350', 'ES 350'],
'Mercedes-Benz' => ['S-Class', 'G-Wagon', 'E-Class'],
'BMW' => ['X7', 'X5', '7 Series'],
'Audi' => ['Q8', 'A8', 'RS7'],
'Land Rover' => ['Defender', 'Range Rover'],
'Porsche' => ['911 Carrera', 'Cayenne'],
'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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
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);
$branch_id = rand(1, 4);
$is_featured = ($i <= 8) ? 1 : 0;
$installment_available = rand(0, 1);
$image_url = "assets/images/cars/car{$i}.jpg";
$vin = "VIN" . str_pad((string)$i, 10, "0", STR_PAD_LEFT);
$stmt->execute([
$vin, $brand, $model, $year, $price, $mileage,
'Automatic', rand(0,1) ? 'Gasoline' : 'Hybrid',
$branch_id, $is_featured, $image_url, $installment_available
]);
}
}
$stmt = $db->query("SELECT COUNT(*) FROM users");
if ($stmt->fetchColumn() == 0) {
$stmt = $db->prepare("INSERT INTO users (username, password, role) VALUES (?, ?, ?)");
$stmt->execute(['admin', password_hash('admin123', PASSWORD_DEFAULT), 'Super Admin']);
}
// Create flag file to prevent re-running on every request
file_put_contents($flagFile, date('Y-m-d H:i:s'));
} catch (Exception $e) {
error_log("DB Setup Failed: " . $e->getMessage());
}
}
?>

19
includes/middleware.php Normal file
View 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;
}
}

View File

@ -0,0 +1,43 @@
<?php
require_once __DIR__ . '/auth.php';
function requireRole($allowed_roles) {
if (!is_array($allowed_roles)) {
$allowed_roles = [$allowed_roles];
}
if (!isLoggedIn()) {
header('Location: /login.php');
exit;
}
$user_role = $_SESSION['role'] ?? 'Guest';
if (!in_array($user_role, $allowed_roles)) {
// Redirect based on their actual role or to home
switch ($user_role) {
case 'Admin':
case 'Super Admin':
header('Location: /admin/index.php');
break;
case 'Dealer':
header('Location: /dealer/index.php');
break;
case 'Customer':
case 'Buyer': // Assuming 'Buyer' is the role name from prompt
header('Location: /buyer/index.php');
break;
default:
header('Location: /index.php');
}
exit;
}
}
function hasRole($role) {
return isset($_SESSION['role']) && $_SESSION['role'] === $role;
}
function isSuperAdmin() {
return hasRole('Super Admin');
}

255
index.php
View File

@ -1,150 +1,117 @@
<?php <?php
declare(strict_types=1); // index.php
@ini_set('display_errors', '1'); require_once 'includes/auth.php';
@error_reporting(E_ALL); require_once 'includes/header.php';
@date_default_timezone_set('UTC'); global $pdo;
$phpVersion = PHP_VERSION; // Fetch Featured Cars
$now = date('Y-m-d H:i:s'); try {
$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 = [];
}
?> ?>
<!doctype html>
<html lang="en"> <!-- Hero Section -->
<head> <section class="hero">
<meta charset="utf-8" /> <div class="container">
<meta name="viewport" content="width=device-width, initial-scale=1" /> <h1>Find Your Dream Car</h1>
<title>New Style</title> <p>Premium Vehicles. Flexible Installments. Nationwide Service.</p>
<?php <a href="marketplace.php" class="btn">Browse Inventory</a>
// Read project preview data from environment </div>
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? ''; </section>
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
?> <!-- Installment Calculator Section -->
<?php if ($projectDescription): ?> <section class="container mt-5">
<!-- Meta description --> <h2 class="text-center">Installment Calculator</h2>
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' /> <div class="calculator">
<!-- Open Graph meta tags --> <div class="grid" style="grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));">
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" /> <div class="form-group">
<!-- Twitter meta tags --> <label>Car Price ($)</label>
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" /> <input type="number" id="calcPrice" class="form-control" placeholder="30000" value="30000">
<?php endif; ?> </div>
<?php if ($projectImageUrl): ?> <div class="form-group">
<!-- Open Graph image --> <label>Down Payment ($)</label>
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" /> <input type="number" id="calcDown" class="form-control" placeholder="5000" value="5000">
<!-- Twitter image --> </div>
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" /> <div class="form-group">
<?php endif; ?> <label>Loan Term</label>
<link rel="preconnect" href="https://fonts.googleapis.com"> <select id="calcMonths" class="form-control">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <option value="12">12 Months</option>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet"> <option value="24" selected>24 Months</option>
<style> <option value="36">36 Months</option>
:root { <option value="48">48 Months</option>
--bg-color-start: #6a11cb; </select>
--bg-color-end: #2575fc; </div>
--text-color: #ffffff; <div class="form-group" style="display: flex; align-items: flex-end;">
--card-bg-color: rgba(255, 255, 255, 0.01); <button onclick="calculateInstallment()" class="btn" style="width: 100%;">Calculate</button>
--card-border-color: rgba(255, 255, 255, 0.1); </div>
} </div>
body { <div id="calcResult" class="mt-5 text-center" style="font-size: 1.5rem; font-weight: bold; color: var(--primary);">
margin: 0; Monthly Payment: $1,145.83
font-family: 'Inter', sans-serif; </div>
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end)); </div>
color: var(--text-color); </section>
display: flex;
justify-content: center; <!-- Featured Cars -->
align-items: center; <section class="container mt-5">
min-height: 100vh; <h2 class="text-center mb-5">Featured Vehicles</h2>
text-align: center; <div class="grid">
overflow: hidden; <?php foreach ($featured_cars as $car): ?>
position: relative;
}
body::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
animation: bg-pan 20s linear infinite;
z-index: -1;
}
@keyframes bg-pan {
0% { background-position: 0% 0%; }
100% { background-position: 100% 100%; }
}
main {
padding: 2rem;
}
.card {
background: var(--card-bg-color);
border: 1px solid var(--card-border-color);
border-radius: 16px;
padding: 2rem;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
}
.loader {
margin: 1.25rem auto 1.25rem;
width: 48px;
height: 48px;
border: 3px solid rgba(255, 255, 255, 0.25);
border-top-color: #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.hint {
opacity: 0.9;
}
.sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap; border: 0;
}
h1 {
font-size: 3rem;
font-weight: 700;
margin: 0 0 1rem;
letter-spacing: -1px;
}
p {
margin: 0.5rem 0;
font-size: 1.1rem;
}
code {
background: rgba(0,0,0,0.2);
padding: 2px 6px;
border-radius: 4px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
footer {
position: absolute;
bottom: 1rem;
font-size: 0.8rem;
opacity: 0.7;
}
</style>
</head>
<body>
<main>
<div class="card"> <div class="card">
<h1>Analyzing your requirements and generating your website…</h1> <img src="<?= htmlspecialchars($car['image_path'] ?? $car['image_url'] ?? '') ?>" alt="<?= htmlspecialchars($car['brand']) ?>">
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes"> <div class="card-body">
<span class="sr-only">Loading…</span> <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>
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p> <div class="card-actions">
<p class="hint">This page will update automatically as the plan is implemented.</p> <a href="car_detail.php?id=<?= $car['id'] ?>" class="btn btn-outline" style="width:100%">View Details</a>
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
</div> </div>
</main> </div>
<footer> </div>
Page updated: <?= htmlspecialchars($now) ?> (UTC) <?php endforeach; ?>
</footer> </div>
</body> </section>
</html>
<!-- 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="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>
<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'; ?>

64
login.php Normal file
View File

@ -0,0 +1,64 @@
<?php
// login.php
require_once 'includes/auth.php';
if (isLoggedIn()) {
header("Location: index.php");
exit;
}
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = $_POST['email'] ?? '';
$password = $_POST['password'] ?? '';
if (login($email, $password)) {
if (getUserRole() === 'admin') {
header("Location: admin/index.php");
} else {
header("Location: index.php");
}
exit;
} else {
$error = "Invalid email or password";
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - AFG CARS</title>
<link rel="stylesheet" href="assets/css/style.css">
</head>
<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 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">
<input type="email" name="email" class="form-control" placeholder="Email Address" required>
</div>
<div class="form-group">
<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>

3
logout.php Normal file
View File

@ -0,0 +1,3 @@
<?php
require_once 'includes/auth.php';
logout();

View File

@ -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);
}
}

View File

@ -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,
];

101
marketplace.php Normal file
View File

@ -0,0 +1,101 @@
<?php
// marketplace.php
require_once 'includes/auth.php';
require_once 'includes/header.php';
global $pdo;
// Fetch unique filter values
try {
$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 = [];
}
?>
<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 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>
<?php endforeach; ?>
<?php else: ?>
<p>No cars found matching your criteria.</p>
<?php endif; ?>
</div>
</div>
<?php require_once 'includes/footer.php'; ?>

106
setup.php Normal file
View File

@ -0,0 +1,106 @@
<?php
// setup.php - Enterprise Setup Script
require_once __DIR__ . '/db/config.php';
echo "<h1>AFG CARS Enterprise Setup</h1>";
try {
global $pdo; // Assumes db/config.php creates $pdo
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>";
}
$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'],
['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 = $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");
$brands = ['Toyota', 'Lexus', 'Mercedes-Benz', 'BMW', 'Audi', 'Land Rover', 'Porsche', 'Tesla'];
$models = [
'Toyota' => ['Camry', 'Land Cruiser', 'Corolla', 'RAV4'],
'Lexus' => ['LX 600', 'RX 350', 'ES 350'],
'Mercedes-Benz' => ['S-Class', 'G-Wagon', 'E-Class'],
'BMW' => ['X7', 'X5', '7 Series'],
'Audi' => ['Q8', 'A8', 'RS7'],
'Land Rover' => ['Defender', 'Range Rover'],
'Porsche' => ['911 Carrera', 'Cayenne'],
'Tesla' => ['Model S', 'Model X']
];
$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(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 <= 6) ? 1 : 0; // First 6 are featured
$stmt->execute([
$brand, $model, $year, $price, $mileage, $fuel, 'Automatic',
$desc, $image, $branch_id, $is_featured, 'available'
]);
}
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) {
die("<h3 style='color:red'>Setup Failed: " . $e->getMessage() . "</h3>");
}

46
work.php Normal file
View File

@ -0,0 +1,46 @@
<?php
// work.php
require_once 'includes/auth.php';
require_once 'includes/header.php';
?>
<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="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 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>
<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 'includes/footer.php'; ?>