Compare commits
No commits in common. "ai-dev" and "master" have entirely different histories.
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,8 +1,3 @@
|
||||
node_modules/
|
||||
*/node_modules/
|
||||
*/build/
|
||||
|
||||
# Composer
|
||||
vendor/
|
||||
composer.lock
|
||||
composer.phar
|
||||
|
||||
92
analyze.php
92
analyze.php
@ -1,92 +0,0 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'db/config.php';
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: login.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
// Check for sufficient credits
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT credits FROM users WHERE id = ?");
|
||||
$stmt->execute([$_SESSION['user_id']]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if (!$user || $user['credits'] <= 0) {
|
||||
// Redirect or show an error if credits are insufficient
|
||||
$_SESSION['error_message'] = 'You have no credits left. Please purchase more to continue.';
|
||||
header('Location: pricing.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['upload_id'])) {
|
||||
$uploadId = $_POST['upload_id'];
|
||||
$userId = $_SESSION['user_id'];
|
||||
|
||||
// Re-verify the upload belongs to the user
|
||||
$stmt = $pdo->prepare("SELECT * FROM uploads WHERE id = ? AND user_id = ?");
|
||||
$stmt->execute([$uploadId, $userId]);
|
||||
$upload = $stmt->fetch();
|
||||
|
||||
if ($upload) {
|
||||
// Deduct one credit BEFORE starting the analysis
|
||||
$pdo->prepare("UPDATE users SET credits = credits - 1 WHERE id = ?")->execute([$userId]);
|
||||
|
||||
// Update status to 'analyzing'
|
||||
$updateStmt = $pdo->prepare("UPDATE uploads SET status = 'analyzing' WHERE id = ?");
|
||||
$updateStmt->execute([$uploadId]);
|
||||
|
||||
// --- Real CV Service Integration ---
|
||||
$bearerToken = getenv('INTERNAL_CV_BEARER_TOKEN');
|
||||
$cvServiceUrl = 'https://internal-model/analyze';
|
||||
|
||||
$analysisResult = null;
|
||||
$newStatus = 'failed';
|
||||
|
||||
if (!$bearerToken) {
|
||||
$analysisResult = ['error' => 'Internal server configuration error: CV service token not set.'];
|
||||
} elseif (empty($upload['file_path']) || !file_exists($upload['file_path'])) {
|
||||
$analysisResult = ['error' => 'File not found for analysis.'];
|
||||
} else {
|
||||
// Prepare cURL request
|
||||
$ch = curl_init();
|
||||
$cfile = new CURLFile($upload['file_path'], mime_content_type($upload['file_path']), basename($upload['file_path']));
|
||||
|
||||
curl_setopt($ch, CURLOPT_URL, $cvServiceUrl);
|
||||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, ['image' => $cfile]);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Authorization: Bearer ' . $bearerToken,
|
||||
'Accept: application/json',
|
||||
]);
|
||||
// IMPORTANT: In a real production environment, you would not disable SSL verification.
|
||||
// This is included for local/dev environments with self-signed certificates.
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlError = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($curlError) {
|
||||
$analysisResult = ['error' => 'cURL Error: ' . $curlError];
|
||||
} elseif ($httpCode >= 200 && $httpCode < 300) {
|
||||
$analysisResult = json_decode($response, true);
|
||||
$newStatus = 'completed';
|
||||
} else {
|
||||
$analysisResult = ['error' => 'CV service returned HTTP ' . $httpCode, 'response' => $response];
|
||||
}
|
||||
}
|
||||
|
||||
// Store the result and update status
|
||||
$resultStmt = $pdo->prepare("UPDATE uploads SET status = ?, analysis_result = ? WHERE id = ?");
|
||||
$resultStmt->execute([$newStatus, json_encode($analysisResult), $uploadId]);
|
||||
}
|
||||
}
|
||||
|
||||
header('Location: index.php');
|
||||
exit();
|
||||
?>
|
||||
@ -1,139 +0,0 @@
|
||||
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
background-color: #F8F9FA;
|
||||
}
|
||||
|
||||
.header-gradient {
|
||||
background: linear-gradient(135deg, #007BFF, #0056b3);
|
||||
}
|
||||
|
||||
.upload-zone {
|
||||
border: 2px dashed #007BFF;
|
||||
border-radius: 0.5rem;
|
||||
padding: 4rem;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.upload-zone.drag-over {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
|
||||
.upload-zone .icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
stroke-width: 1.5;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.result-card {
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
/* Auth Forms */
|
||||
.auth-form {
|
||||
max-width: 400px;
|
||||
margin: 5rem auto;
|
||||
padding: 2rem;
|
||||
background: #fff;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.auth-form h1 {
|
||||
text-align: center;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
background-color: #007bff;
|
||||
border: 1px solid #007bff;
|
||||
padding: 0.75rem 1.25rem;
|
||||
font-size: 1rem;
|
||||
border-radius: 0.25rem;
|
||||
transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: #0069d9;
|
||||
border-color: #0062cc;
|
||||
}
|
||||
|
||||
.alert {
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.alert-danger {
|
||||
color: #721c24;
|
||||
background-color: #f8d7da;
|
||||
border-color: #f5c6cb;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
color: #155724;
|
||||
background-color: #d4edda;
|
||||
border-color: #c3e6cb;
|
||||
}
|
||||
|
||||
.upload-card {
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
transition: transform 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.upload-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.upload-card .card-img-top {
|
||||
aspect-ratio: 16 / 9;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.analysis-result {
|
||||
background-color: #f8f9fa;
|
||||
border-left: 3px solid #007BFF;
|
||||
padding: 0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.bounding-box {
|
||||
position: absolute;
|
||||
border: 2px solid #ff4d4d;
|
||||
background-color: rgba(255, 77, 77, 0.2);
|
||||
box-shadow: 0 0 5px rgba(255, 77, 77, 0.5);
|
||||
pointer-events: none; /* So it doesn't interfere with image interactions */
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const uploadZone = document.getElementById('uploadZone');
|
||||
const fileInput = document.getElementById('vehicleImage');
|
||||
const uploadForm = document.getElementById('uploadForm');
|
||||
|
||||
if (uploadZone) {
|
||||
uploadZone.addEventListener('click', () => fileInput.click());
|
||||
|
||||
uploadZone.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
uploadZone.classList.add('drag-over');
|
||||
});
|
||||
|
||||
uploadZone.addEventListener('dragleave', () => {
|
||||
uploadZone.classList.remove('drag-over');
|
||||
});
|
||||
|
||||
uploadZone.addEventListener('drop', (e) => {
|
||||
e.preventDefault();
|
||||
uploadZone.classList.remove('drag-over');
|
||||
if (e.dataTransfer.files.length > 0) {
|
||||
fileInput.files = e.dataTransfer.files;
|
||||
// Automatically submit the form when a file is dropped
|
||||
uploadForm.submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
81
billing.php
81
billing.php
@ -1,81 +0,0 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/partials/header.php';
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$userId = $_SESSION['user_id'];
|
||||
$pdo = db();
|
||||
|
||||
// Fetch user's current credit balance
|
||||
$stmt = $pdo->prepare("SELECT credits FROM users WHERE id = ?");
|
||||
$stmt->execute([$userId]);
|
||||
$user = $stmt->fetch();
|
||||
$user_credits = $user ? $user['credits'] : 0;
|
||||
|
||||
// Fetch purchase history
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT p.credits_purchased, p.amount_paid, p.created_at, pl.name as plan_name " .
|
||||
"FROM purchases p " .
|
||||
"JOIN plans pl ON p.plan_id = pl.id " .
|
||||
"WHERE p.user_id = ? ORDER BY p.created_at DESC"
|
||||
);
|
||||
$stmt->execute([$userId]);
|
||||
$purchases = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
?>
|
||||
|
||||
<div class="container">
|
||||
<div class="text-center my-5">
|
||||
<h1>Billing & Credits</h1>
|
||||
<p class="lead">View your credit balance and purchase history.</p>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="card-title">Your Credits</h5>
|
||||
<p class="display-4 fw-bold"><?= $user_credits ?></p>
|
||||
<a href="pricing.php" class="btn btn-primary">Buy More Credits</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Purchase History</h5>
|
||||
<?php if ($purchases): ?>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Package</th>
|
||||
<th>Credits</th>
|
||||
<th>Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($purchases as $purchase): ?>
|
||||
<tr>
|
||||
<td><?= date('M d, Y', strtotime($purchase['created_at'])) ?></td>
|
||||
<td><?= htmlspecialchars($purchase['plan_name']) ?></td>
|
||||
<td>+<?= htmlspecialchars($purchase['credits_purchased']) ?></td>
|
||||
<td>$<?= htmlspecialchars(number_format($purchase['amount_paid'], 2)) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php else: ?>
|
||||
<p>You have not made any purchases yet.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once __DIR__ . '/partials/footer.php'; ?>
|
||||
40
checkout.php
40
checkout.php
@ -1,40 +0,0 @@
|
||||
<?php
|
||||
require_once 'vendor/autoload.php';
|
||||
require_once 'db/config.php';
|
||||
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$stripe_secret_key = getenv('STRIPE_SECRET_KEY');
|
||||
if (!$stripe_secret_key) {
|
||||
die('Stripe secret key is not configured.');
|
||||
}
|
||||
|
||||
\Stripe\Stripe::setApiKey($stripe_secret_key);
|
||||
|
||||
$price_id = $_GET['price_id'] ?? null;
|
||||
if (!$price_id) {
|
||||
header('Location: pricing.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$user_email = $_SESSION['user_email']; // Assuming user_email is stored in session from login
|
||||
|
||||
$checkout_session = \Stripe\Checkout\Session::create([
|
||||
'payment_method_types' => ['card'],
|
||||
'line_items' => [[
|
||||
'price' => $price_id,
|
||||
'quantity' => 1,
|
||||
]],
|
||||
'mode' => 'subscription',
|
||||
'success_url' => 'http://' . $_SERVER['HTTP_HOST'] . '/index.php?payment=success',
|
||||
'cancel_url' => 'http://' . $_SERVER['HTTP_HOST'] . '/pricing.php?payment=cancel',
|
||||
'customer_email' => $user_email,
|
||||
'client_reference_id' => $_SESSION['user_id']
|
||||
]);
|
||||
|
||||
header("Location: " . $checkout_session->url);
|
||||
@ -1,13 +0,0 @@
|
||||
{
|
||||
"name": "flatlogic/vda",
|
||||
"description": "Vehicle Damage Analysis",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Flatlogic Bot",
|
||||
"email": "support@flatlogic.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"stripe/stripe-php": "^18.2"
|
||||
}
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
<?php
|
||||
require_once 'vendor/autoload.php';
|
||||
require_once 'db/config.php';
|
||||
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Load Stripe API key from .env
|
||||
$stripeSecretKey = getenv('STRIPE_SECRET_KEY');
|
||||
if (!$stripeSecretKey) {
|
||||
die('Stripe secret key is not configured.');
|
||||
}
|
||||
|
||||
\Stripe\Stripe::setApiKey($stripeSecretKey);
|
||||
|
||||
// Get the user's Stripe Customer ID from your database
|
||||
$userId = $_SESSION['user_id'];
|
||||
$customerId = null;
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT stripe_customer_id FROM subscriptions WHERE user_id = ? ORDER BY created_at DESC LIMIT 1");
|
||||
$stmt->execute([$userId]);
|
||||
$customerId = $stmt->fetchColumn();
|
||||
} catch (PDOException $e) {
|
||||
die('Could not retrieve customer data.');
|
||||
}
|
||||
|
||||
if (!$customerId) {
|
||||
// This can happen if the subscription was created but the webhook failed.
|
||||
// Or if the user has no subscription.
|
||||
header('Location: billing.php?error=nocustomer');
|
||||
exit;
|
||||
}
|
||||
|
||||
// The return URL to which the user will be redirected after managing their billing
|
||||
$returnUrl = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . '/billing.php';
|
||||
|
||||
try {
|
||||
// Create a Billing Portal session
|
||||
$portalSession = \Stripe\BillingPortal\Session::create([
|
||||
'customer' => $customerId,
|
||||
'return_url' => $returnUrl,
|
||||
]);
|
||||
|
||||
// Redirect to the session URL
|
||||
header("Location: " . $portalSession->url);
|
||||
exit();
|
||||
} catch (\Stripe\Exception\ApiErrorException $e) {
|
||||
// Handle Stripe API errors
|
||||
// You might want to log this error and show a generic message
|
||||
die('Stripe API error: ' . $e->getMessage());
|
||||
}
|
||||
@ -15,16 +15,3 @@ function db() {
|
||||
}
|
||||
return $pdo;
|
||||
}
|
||||
|
||||
function hasActiveSubscription($user_id) {
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT id FROM subscriptions WHERE user_id = ? AND status = 'active' AND current_period_end > NOW()");
|
||||
$stmt->execute([$user_id]);
|
||||
return $stmt->fetch() !== false;
|
||||
} catch (PDOException $e) {
|
||||
// Log error if needed
|
||||
error_log('Subscription check failed: ' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,49 +0,0 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/config.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
// 1. Create migrations table if it doesn't exist
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS `migrations` ( `id` INT AUTO_INCREMENT PRIMARY KEY, `migration` VARCHAR(255) NOT NULL, `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP );");
|
||||
|
||||
// 2. Get all executed migrations
|
||||
$stmt = $pdo->query("SELECT `migration` FROM `migrations`");
|
||||
$executed_migrations = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
// 3. Find and execute new migrations
|
||||
$migration_files = glob(__DIR__ . '/migrations/*.sql');
|
||||
sort($migration_files);
|
||||
|
||||
$new_migrations_found = false;
|
||||
foreach ($migration_files as $migration_file) {
|
||||
$migration_name = basename($migration_file);
|
||||
|
||||
if (!in_array($migration_name, $executed_migrations)) {
|
||||
$new_migrations_found = true;
|
||||
$sql = file_get_contents($migration_file);
|
||||
|
||||
try {
|
||||
$pdo->exec($sql);
|
||||
|
||||
// 4. Log the new migration
|
||||
$insert_stmt = $pdo->prepare("INSERT INTO `migrations` (`migration`) VALUES (?)");
|
||||
$insert_stmt->execute([$migration_name]);
|
||||
|
||||
echo "Executed migration: " . $migration_name . PHP_EOL;
|
||||
} catch (PDOException $e) {
|
||||
// If a specific migration fails, output the error and stop.
|
||||
die("Migration failed on " . $migration_name . ": " . $e->getMessage() . PHP_EOL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$new_migrations_found) {
|
||||
echo "No new migrations to execute." . PHP_EOL;
|
||||
} else {
|
||||
echo "All new migrations executed successfully." . PHP_EOL;
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
die("Database operation failed: " . $e->getMessage() . PHP_EOL);
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS `users` (
|
||||
`id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
`email` VARCHAR(255) NOT NULL UNIQUE,
|
||||
`password` VARCHAR(255) NOT NULL,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
@ -1,7 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS `uploads` (
|
||||
`id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
`user_id` INT(11) UNSIGNED NOT NULL,
|
||||
`file_path` VARCHAR(255) NOT NULL,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
@ -1,3 +0,0 @@
|
||||
ALTER TABLE `uploads`
|
||||
ADD COLUMN `status` VARCHAR(50) NOT NULL DEFAULT 'pending',
|
||||
ADD COLUMN `analysis_result` TEXT DEFAULT NULL;
|
||||
@ -1,21 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS `plans` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`name` VARCHAR(255) NOT NULL,
|
||||
`stripe_price_id` VARCHAR(255) NOT NULL,
|
||||
`price` DECIMAL(10, 2) NOT NULL,
|
||||
`features` TEXT,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `subscriptions` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`user_id` INT(11) UNSIGNED NOT NULL,
|
||||
`plan_id` INT NOT NULL,
|
||||
`stripe_subscription_id` VARCHAR(255) NOT NULL,
|
||||
`status` VARCHAR(50) NOT NULL,
|
||||
`current_period_end` TIMESTAMP,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`),
|
||||
FOREIGN KEY (`plan_id`) REFERENCES `plans`(`id`)
|
||||
);
|
||||
@ -1,2 +0,0 @@
|
||||
ALTER TABLE `users` ADD `credits` INT UNSIGNED NOT NULL DEFAULT 0;
|
||||
ALTER TABLE `plans` ADD `credits_awarded` INT UNSIGNED NOT NULL DEFAULT 0;
|
||||
@ -1,15 +0,0 @@
|
||||
-- Drop the now-obsolete subscriptions table
|
||||
DROP TABLE IF EXISTS `subscriptions`;
|
||||
|
||||
-- Create a table to log credit purchases
|
||||
CREATE TABLE IF NOT EXISTS `purchases` (
|
||||
`id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
`user_id` INT(11) UNSIGNED NOT NULL,
|
||||
`plan_id` INT NOT NULL,
|
||||
`stripe_charge_id` VARCHAR(255) NOT NULL,
|
||||
`credits_purchased` INT(11) NOT NULL,
|
||||
`amount_paid` DECIMAL(10, 2) NOT NULL,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`plan_id`) REFERENCES `plans`(`id`) ON DELETE RESTRICT
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
@ -1,31 +0,0 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/config.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
// Check if plans already exist
|
||||
$stmt = $pdo->query("SELECT COUNT(*) FROM plans");
|
||||
if ($stmt->fetchColumn() > 0) {
|
||||
echo "Plans table is already seeded." . PHP_EOL;
|
||||
exit;
|
||||
}
|
||||
|
||||
// Insert the "Starter" credit pack
|
||||
// IMPORTANT: Replace 'price_12345' with your actual Stripe Price ID for the credit pack
|
||||
$starterPack = [
|
||||
'name' => 'Starter Pack',
|
||||
'stripe_price_id' => 'price_1PeP3QRpH4kRz8A8e25a25fA', // Placeholder - REPLACE THIS
|
||||
'price' => 5.00,
|
||||
'credits_awarded' => 50,
|
||||
'features' => json_encode(['50 analysis credits', 'Standard support']),
|
||||
];
|
||||
|
||||
$sql = "INSERT INTO plans (name, stripe_price_id, price, credits_awarded, features) VALUES (:name, :stripe_price_id, :price, :credits_awarded, :features)";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($starterPack);
|
||||
|
||||
echo "Successfully seeded the 'plans' table with the Starter Pack." . PHP_EOL;
|
||||
|
||||
} catch (PDOException $e) {
|
||||
die("Database error: " . $e->getMessage());
|
||||
}
|
||||
329
index.php
329
index.php
@ -1,195 +1,150 @@
|
||||
<?php
|
||||
require_once 'partials/header.php';
|
||||
require_once 'db/config.php';
|
||||
declare(strict_types=1);
|
||||
@ini_set('display_errors', '1');
|
||||
@error_reporting(E_ALL);
|
||||
@date_default_timezone_set('UTC');
|
||||
|
||||
// If the user is not logged in, redirect to the login page.
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT credits FROM users WHERE id = ?");
|
||||
$stmt->execute([$_SESSION['user_id']]);
|
||||
$user = $stmt->fetch();
|
||||
$user_credits = $user ? $user['credits'] : 0;
|
||||
|
||||
$upload_dir = 'uploads/';
|
||||
$uploaded_file_path = null;
|
||||
$error_message = null;
|
||||
|
||||
if ($user_credits > 0 && $_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['vehicleImage'])) {
|
||||
if ($_FILES['vehicleImage']['error'] === UPLOAD_ERR_OK) {
|
||||
$tmp_name = $_FILES['vehicleImage']['tmp_name'];
|
||||
$name = basename($_FILES['vehicleImage']['name']);
|
||||
$file_ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
|
||||
$allowed_ext = ['jpg', 'jpeg', 'png', 'gif'];
|
||||
|
||||
if (in_array($file_ext, $allowed_ext)) {
|
||||
// Create a unique filename to avoid conflicts
|
||||
$new_filename = uniqid('', true) . '.' . $file_ext;
|
||||
$destination = $upload_dir . $new_filename;
|
||||
|
||||
if (move_uploaded_file($tmp_name, $destination)) {
|
||||
$uploaded_file_path = $destination;
|
||||
try {
|
||||
$stmt = db()->prepare("INSERT INTO uploads (user_id, file_path) VALUES (?, ?)");
|
||||
$stmt->execute([$_SESSION['user_id'], $uploaded_file_path]);
|
||||
} catch (PDOException $e) {
|
||||
$error_message = "Database error: " . $e->getMessage();
|
||||
// Optionally, delete the uploaded file if DB insertion fails
|
||||
unlink($destination);
|
||||
}
|
||||
} else {
|
||||
$error_message = 'Failed to move uploaded file.';
|
||||
}
|
||||
} else {
|
||||
$error_message = 'Invalid file type. Please upload a JPG, PNG, or GIF image.';
|
||||
}
|
||||
} else {
|
||||
$error_message = 'File upload failed with error code: ' . $_FILES['vehicleImage']['error'];
|
||||
}
|
||||
}
|
||||
$phpVersion = PHP_VERSION;
|
||||
$now = date('Y-m-d H:i:s');
|
||||
?>
|
||||
|
||||
|
||||
<main class="container">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 class="h2">My Dashboard</h1>
|
||||
<div class="text-end">
|
||||
<span class="badge bg-primary fs-6">Credits: <?= $user_credits ?></span>
|
||||
<a href="pricing.php" class="btn btn-sm btn-outline-primary ms-2">Buy More</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($user_credits > 0): ?>
|
||||
<div class="card p-4 p-md-5 border-0 shadow-sm mb-5">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-lg-6 mb-4 mb-lg-0">
|
||||
<h2 class="h1 mb-3">Upload Vehicle Photo</h2>
|
||||
<p class="lead mb-4">Get an instant AI-powered damage analysis. Drag and drop an image or click to select a file.</p>
|
||||
<form id="uploadForm" action="index.php" method="post" enctype="multipart/form-data">
|
||||
<div id="uploadZone" class="upload-zone">
|
||||
<i data-feather="upload-cloud" class="icon text-primary"></i>
|
||||
<p class="m-0"><strong>Drag & drop</strong> or <strong>click to browse</strong></p>
|
||||
<p class="text-muted small">Supports: JPG, PNG, GIF</p>
|
||||
</div>
|
||||
<input type="file" name="vehicleImage" id="vehicleImage" class="d-none">
|
||||
<button type="submit" class="btn btn-primary btn-lg mt-3">Upload Image</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<?php if ($error_message): ?>
|
||||
<div class="alert alert-danger">
|
||||
<?php echo htmlspecialchars($error_message); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($uploaded_file_path): ?>
|
||||
<div class="text-center">
|
||||
<h3 class="h5 mb-3">Analysis Result</h3>
|
||||
<div class="card result-card">
|
||||
<img src="<?php echo htmlspecialchars($uploaded_file_path); ?>" class="card-img-top" alt="Uploaded Vehicle Image">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Image Received</h5>
|
||||
<p class="card-text d-flex align-items-center justify-content-center">
|
||||
Status: <span class="badge bg-info text-dark ms-2">Pending Analysis</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="card p-4 p-md-5 border-0 shadow-sm mb-5 text-center">
|
||||
<h2 class="h1 mb-3">You're Out of Credits!</h2>
|
||||
<p class="lead mb-4">You need to buy more credits to upload and analyze images.</p>
|
||||
<a href="pricing.php" class="btn btn-primary btn-lg">Buy Credits</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="dashboard">
|
||||
<h2 class="h3 mb-4">My Uploads</h2>
|
||||
<div class="row">
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>New Style</title>
|
||||
<?php
|
||||
$stmt = db()->prepare("SELECT * FROM uploads WHERE user_id = ? ORDER BY created_at DESC");
|
||||
$stmt->execute([$_SESSION['user_id']]);
|
||||
$uploads = $stmt->fetchAll();
|
||||
|
||||
if (count($uploads) > 0):
|
||||
foreach ($uploads as $upload):
|
||||
$status = htmlspecialchars($upload['status']);
|
||||
$status_badge_class = 'bg-secondary';
|
||||
if ($status === 'pending') {
|
||||
$status_badge_class = 'bg-warning text-dark';
|
||||
} elseif ($status === 'analyzing') {
|
||||
$status_badge_class = 'bg-info text-dark';
|
||||
} elseif ($status === 'completed') {
|
||||
$status_badge_class = 'bg-success';
|
||||
} elseif ($status === 'failed') {
|
||||
$status_badge_class = 'bg-danger';
|
||||
// Read project preview data from environment
|
||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
?>
|
||||
<?php if ($projectDescription): ?>
|
||||
<!-- Meta description -->
|
||||
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
|
||||
<!-- Open Graph meta tags -->
|
||||
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<!-- Twitter meta tags -->
|
||||
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<?php endif; ?>
|
||||
<?php if ($projectImageUrl): ?>
|
||||
<!-- Open Graph image -->
|
||||
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<!-- Twitter image -->
|
||||
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<?php endif; ?>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg-color-start: #6a11cb;
|
||||
--bg-color-end: #2575fc;
|
||||
--text-color: #ffffff;
|
||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
?>
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card upload-card h-100">
|
||||
<img src="<?php echo htmlspecialchars($upload['file_path']); ?>" class="card-img-top" alt="Uploaded Image">
|
||||
<div class="card-body d-flex flex-column">
|
||||
<p class="card-text small text-muted mb-2">
|
||||
Uploaded: <?php echo date("M d, Y", strtotime($upload['created_at'])); ?>
|
||||
</p>
|
||||
<p class="card-text mb-3">
|
||||
Status: <span class="badge <?php echo $status_badge_class; ?>"><?php echo ucfirst($status); ?></span>
|
||||
</p>
|
||||
|
||||
<?php
|
||||
$result = $upload['analysis_result'] ? json_decode($upload['analysis_result'], true) : null;
|
||||
|
||||
if ($status === 'completed' && $result && !isset($result['error'])):
|
||||
?>
|
||||
<div class="analysis-result small mb-3">
|
||||
<strong>Analysis Result:</strong><br>
|
||||
Damage Detected: <?php echo isset($result['damage_detected']) && $result['damage_detected'] ? 'Yes' : 'No'; ?><br>
|
||||
Confidence: <?php echo $result['confidence'] ?? 'N/A'; ?>%<br>
|
||||
</div>
|
||||
<?php elseif ($status === 'failed' && $result && isset($result['error'])):
|
||||
?>
|
||||
<div class="analysis-result small text-danger mb-3">
|
||||
<strong>Analysis Failed:</strong><br>
|
||||
<?php echo htmlspecialchars($result['error']); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="mt-auto">
|
||||
<?php if ($user_credits > 0): ?>
|
||||
<?php if ($status === 'pending' || $status === 'failed'): ?>
|
||||
<form action="analyze.php" method="post" class="d-grid">
|
||||
<input type="hidden" name="upload_id" value="<?php echo $upload['id']; ?>">
|
||||
<button type="submit" class="btn btn-primary btn-sm"><?php echo ($status === 'failed') ? 'Retry Analysis' : 'Analyze'; ?></button>
|
||||
</form>
|
||||
<?php elseif ($status === 'analyzing'): ?>
|
||||
<button type="button" class="btn btn-secondary btn-sm w-100" disabled>Analyzing...</button>
|
||||
<?php elseif ($status === 'completed'): ?>
|
||||
<a href="report.php?id=<?php echo $upload['id']; ?>" class="btn btn-outline-secondary btn-sm">View Report</a>
|
||||
<?php endif; ?>
|
||||
<?php else: ?>
|
||||
<a href="pricing.php" class="btn btn-primary btn-sm disabled" title="Buy more credits">Analyze</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
endforeach;
|
||||
else:
|
||||
?>
|
||||
<div class="col">
|
||||
<p>You haven't uploaded any images yet.</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Inter', sans-serif;
|
||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
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">
|
||||
<h1>Analyzing your requirements and generating your website…</h1>
|
||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
||||
<span class="sr-only">Loading…</span>
|
||||
</div>
|
||||
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
|
||||
<p class="hint">This page will update automatically as the plan is implemented.</p>
|
||||
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<?php require_once 'partials/footer.php'; ?>
|
||||
<footer>
|
||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
70
login.php
70
login.php
@ -1,70 +0,0 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
require_once 'partials/header.php';
|
||||
|
||||
// If user is already logged in, redirect to home
|
||||
if (isset($_SESSION['user_id'])) {
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$errors = [];
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$email = $_POST['email'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
|
||||
if (empty($email) || empty($password)) {
|
||||
$errors[] = 'Email and password are required.';
|
||||
} else {
|
||||
try {
|
||||
$pdo = db();
|
||||
$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_email'] = $user['email'];
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
} else {
|
||||
$errors[] = 'Invalid email or password.';
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$errors[] = 'Database error: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="auth-form">
|
||||
<h1>Login</h1>
|
||||
|
||||
<?php if (!empty($errors)):
|
||||
?>
|
||||
<div class="alert alert-danger">
|
||||
<?php foreach ($errors as $error):
|
||||
?>
|
||||
<p><?php echo htmlspecialchars($error); ?></p>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="login.php" method="post">
|
||||
<div class="form-group">
|
||||
<label for="email">Email</label>
|
||||
<input type="email" id="email" name="email" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
</div>
|
||||
<button type="submit" class="btn">Login</button>
|
||||
</form>
|
||||
<p>Don't have an account? <a href="register.php">Register here</a>.</p>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
require_once 'partials/footer.php';
|
||||
?>
|
||||
@ -1,6 +0,0 @@
|
||||
<?php
|
||||
session_start();
|
||||
session_unset();
|
||||
session_destroy();
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
@ -1,5 +0,0 @@
|
||||
</div> <!-- /container -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,48 +0,0 @@
|
||||
<?php
|
||||
session_start();
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vehicle Damage Analysis</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="index.php">Vehicle Damage AI</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="index.php">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="pricing.php">Pricing</a>
|
||||
</li>
|
||||
<?php if (isset($_SESSION['user_id'])): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="billing.php">Billing</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="logout.php">Logout</a>
|
||||
</li>
|
||||
<?php else: ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="login.php">Login</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="register.php">Register</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container mt-4">
|
||||
39
pricing.php
39
pricing.php
@ -1,39 +0,0 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/partials/header.php';
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
$pdo = db();
|
||||
$stmt = $pdo->query("SELECT * FROM plans ORDER BY price");
|
||||
$plans = $stmt->fetchAll();
|
||||
|
||||
?>
|
||||
|
||||
<div class="container">
|
||||
<div class="text-center my-5">
|
||||
<h1>Purchase Credits</h1>
|
||||
<p class="lead">One credit buys you one image analysis.</p>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<?php foreach ($plans as $plan): ?>
|
||||
<div class="col-md-4">
|
||||
<div class="card mb-4 shadow-sm">
|
||||
<div class="card-header">
|
||||
<h4 class="my-0 fw-normal"><?= htmlspecialchars($plan['name']) ?></h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h1 class="card-title pricing-card-title">$<?= htmlspecialchars(number_format($plan['price'], 2)) ?></h1>
|
||||
<ul class="list-unstyled mt-3 mb-4">
|
||||
<li><strong><?= htmlspecialchars($plan['credits_awarded']) ?></strong> credits</li>
|
||||
<li>Never expire</li>
|
||||
<li>Use them anytime</li>
|
||||
</ul>
|
||||
<a href="checkout.php?price_id=<?= htmlspecialchars($plan['stripe_price_id']) ?>" class="w-100 btn btn-lg btn-primary">Buy Now</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once __DIR__ . '/partials/footer.php'; ?>
|
||||
77
register.php
77
register.php
@ -1,77 +0,0 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
require_once 'partials/header.php';
|
||||
|
||||
$errors = [];
|
||||
$success = '';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$email = $_POST['email'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
|
||||
if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
$errors[] = 'A valid email is required.';
|
||||
}
|
||||
|
||||
if (empty($password) || strlen($password) < 8) {
|
||||
$errors[] = 'Password must be at least 8 characters long.';
|
||||
}
|
||||
|
||||
if (empty($errors)) {
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT id FROM users WHERE email = ?");
|
||||
$stmt->execute([$email]);
|
||||
if ($stmt->fetch()) {
|
||||
$errors[] = 'Email already exists.';
|
||||
} else {
|
||||
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
|
||||
$stmt = $pdo->prepare("INSERT INTO users (email, password) VALUES (?, ?)");
|
||||
$stmt->execute([$email, $hashed_password]);
|
||||
$success = 'Registration successful! You can now <a href="login.php">log in</a>.';
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$errors[] = 'Database error: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="auth-form">
|
||||
<h1>Register</h1>
|
||||
|
||||
<?php if (!empty($errors)):
|
||||
?>
|
||||
<div class="alert alert-danger">
|
||||
<?php foreach ($errors as $error):
|
||||
?>
|
||||
<p><?php echo htmlspecialchars($error); ?></p>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($success):
|
||||
?>
|
||||
<div class="alert alert-success">
|
||||
<p><?php echo $success; ?></p>
|
||||
</div>
|
||||
<?php else:
|
||||
?>
|
||||
<form action="register.php" method="post">
|
||||
<div class="form-group">
|
||||
<label for="email">Email</label>
|
||||
<input type="email" id="email" name="email" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
</div>
|
||||
<button type="submit" class="btn">Register</button>
|
||||
</form>
|
||||
<p>Already have an account? <a href="login.php">Login here</a>.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
require_once 'partials/footer.php';
|
||||
?>
|
||||
94
report.php
94
report.php
@ -1,94 +0,0 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'db/config.php';
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: login.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
// Check for active subscription
|
||||
if (!hasActiveSubscription($_SESSION['user_id'])) {
|
||||
// Redirect to billing page if no active subscription
|
||||
header('Location: billing.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
if (!isset($_GET['id'])) {
|
||||
header('Location: index.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
$uploadId = $_GET['id'];
|
||||
$userId = $_SESSION['user_id'];
|
||||
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT * FROM uploads WHERE id = ? AND user_id = ? AND status = 'completed'");
|
||||
$stmt->execute([$uploadId, $userId]);
|
||||
$upload = $stmt->fetch();
|
||||
|
||||
if (!$upload) {
|
||||
// Redirect if the upload doesn't exist, doesn't belong to the user, or isn't completed
|
||||
header('Location: index.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
$analysisResult = json_decode($upload['analysis_result'], true);
|
||||
$imagePath = htmlspecialchars($upload['file_path']);
|
||||
list($width, $height) = getimagesize($upload['file_path']);
|
||||
|
||||
include 'partials/header.php';
|
||||
?>
|
||||
|
||||
<main class="container mt-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2 class="h3">Analysis Report</h2>
|
||||
<a href="index.php" class="btn btn-outline-secondary">Back to Dashboard</a>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-7">
|
||||
<h4 class="mb-3">Vehicle Image</h4>
|
||||
<div id="image-container" class="position-relative" style="width: <?php echo $width; ?>px; max-width: 100%;">
|
||||
<img src="<?php echo $imagePath; ?>" class="img-fluid" alt="Analyzed Vehicle Image">
|
||||
<?php if (isset($analysisResult['bounding_box'])):
|
||||
$box = $analysisResult['bounding_box'];
|
||||
// Calculate percentages for responsive scaling
|
||||
$left = ($box['x'] / $width) * 100;
|
||||
$top = ($box['y'] / $height) * 100;
|
||||
$boxWidth = ($box['width'] / $width) * 100;
|
||||
$boxHeight = ($box['height'] / $height) * 100;
|
||||
?>
|
||||
<div class="bounding-box" style="left: <?php echo $left; ?>%; top: <?php echo $top; ?>%; width: <?php echo $boxWidth; ?>%; height: <?php echo $boxHeight; ?>%;"></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<h4 class="mb-3">Report Details</h4>
|
||||
<?php if ($analysisResult): ?>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered">
|
||||
<?php foreach ($analysisResult as $key => $value):
|
||||
if (is_array($value)) continue; // Skip arrays like bounding_box
|
||||
?>
|
||||
<tr>
|
||||
<th class="w-50"><strong><?php echo ucfirst(str_replace('_', ' ', htmlspecialchars($key))); ?></strong></th>
|
||||
<td><?php echo is_bool($value) ? ($value ? 'Yes' : 'No') : htmlspecialchars($value); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</table>
|
||||
</div>
|
||||
<h5 class="mt-4">Raw JSON Output</h5>
|
||||
<pre class="bg-light p-3 rounded"><code><?php echo json_encode($analysisResult, JSON_PRETTY_PRINT); ?></code></pre>
|
||||
<?php else: ?>
|
||||
<p>No analysis data available.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<?php include 'partials/footer.php'; ?>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 210 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 149 KiB |
92
webhook.php
92
webhook.php
@ -1,92 +0,0 @@
|
||||
<?php
|
||||
require_once 'vendor/autoload.php';
|
||||
require_once 'db/config.php';
|
||||
|
||||
// Get Stripe keys from environment
|
||||
$stripeSecretKey = getenv('STRIPE_SECRET_KEY');
|
||||
$webhookSecret = getenv('STRIPE_WEBHOOK_SECRET');
|
||||
|
||||
if (!$stripeSecretKey || !$webhookSecret) {
|
||||
http_response_code(500);
|
||||
error_log('Stripe keys are not configured.');
|
||||
exit('Configuration error.');
|
||||
}
|
||||
|
||||
\Stripe\Stripe::setApiKey($stripeSecretKey);
|
||||
|
||||
$payload = @file_get_contents('php://input');
|
||||
$sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE'];
|
||||
$event = null;
|
||||
|
||||
try {
|
||||
$event = \Stripe\Webhook::constructEvent(
|
||||
$payload, $sig_header, $webhookSecret
|
||||
);
|
||||
} catch(\UnexpectedValueException $e) {
|
||||
http_response_code(400);
|
||||
exit(); // Invalid payload
|
||||
} catch(\Stripe\Exception\SignatureVerificationException $e) {
|
||||
http_response_code(400);
|
||||
exit(); // Invalid signature
|
||||
}
|
||||
|
||||
// Handle the event
|
||||
switch ($event->type) {
|
||||
case 'checkout.session.completed':
|
||||
$session = $event->data->object;
|
||||
handleCheckoutSession($session);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Unexpected event type
|
||||
error_log('Received unknown event type ' . $event->type);
|
||||
}
|
||||
|
||||
http_response_code(200);
|
||||
|
||||
function handleCheckoutSession($session) {
|
||||
$userId = $session->client_reference_id;
|
||||
$stripeChargeId = $session->payment_intent; // Using payment_intent as a proxy for charge ID
|
||||
|
||||
if (!$userId) {
|
||||
error_log('Webhook Error: No client_reference_id in checkout.session.completed');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
// Retrieve the line items to find out what was purchased
|
||||
$line_items = \Stripe\Checkout\Session::allLineItems($session->id, ['limit' => 1]);
|
||||
$priceId = $line_items->data[0]->price->id;
|
||||
|
||||
// Get plan details from our database
|
||||
$stmt = $pdo->prepare("SELECT id, credits_awarded, price FROM plans WHERE stripe_price_id = ?");
|
||||
$stmt->execute([$priceId]);
|
||||
$plan = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$plan) {
|
||||
error_log("Webhook Error: Plan with price ID {$priceId} not found in database.");
|
||||
return;
|
||||
}
|
||||
|
||||
$planId = $plan['id'];
|
||||
$creditsPurchased = $plan['credits_awarded'];
|
||||
$amountPaid = $plan['price'];
|
||||
|
||||
// Record the purchase
|
||||
$sql = "INSERT INTO purchases (user_id, plan_id, stripe_charge_id, credits_purchased, amount_paid) VALUES (?, ?, ?, ?, ?)";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$userId, $planId, $stripeChargeId, $creditsPurchased, $amountPaid]);
|
||||
|
||||
// Add credits to the user's account
|
||||
$sql = "UPDATE users SET credits = credits + ? WHERE id = ?";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$creditsPurchased, $userId]);
|
||||
|
||||
} catch (\Stripe\Exception\ApiErrorException $e) {
|
||||
error_log("Stripe API Error in webhook: " . $e->getMessage());
|
||||
} catch (PDOException $e) {
|
||||
error_log("Database error in webhook: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user