mock backend

This commit is contained in:
Flatlogic Bot 2025-11-25 07:51:50 +00:00
parent 9470b78cdc
commit 31ce78aca9
5 changed files with 217 additions and 3 deletions

View File

@ -41,4 +41,79 @@ document.addEventListener('DOMContentLoaded', function () {
].join(''); ].join('');
alertContainer.append(wrapper); alertContainer.append(wrapper);
} }
// --- Search Functionality ---
const searchForm = document.getElementById('searchForm');
if (searchForm) {
const searchInput = document.getElementById('searchInput');
const searchButton = document.getElementById('searchButton');
const searchResultsSection = document.getElementById('searchResults');
const resultsContainer = document.getElementById('resultsContainer');
const noResults = document.getElementById('noResults');
searchForm.addEventListener('submit', function(e) {
e.preventDefault();
const query = searchInput.value.trim();
if (query === '') {
return;
}
// Show loading state
searchButton.disabled = true;
searchButton.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Searching...';
resultsContainer.innerHTML = '';
noResults.style.display = 'none';
searchResultsSection.style.display = 'block';
fetch(`search.php?q=${encodeURIComponent(query)}`)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
if (data.length > 0) {
displayResults(data);
} else {
noResults.style.display = 'block';
}
})
.catch(error => {
console.error('Search error:', error);
resultsContainer.innerHTML = '<p class="text-danger text-center">An error occurred while searching. Please try again.</p>';
})
.finally(() => {
// Restore button state
searchButton.disabled = false;
searchButton.innerHTML = 'Search';
});
});
function displayResults(products) {
products.forEach(product => {
const pricesHtml = product.prices.map(p => `
<li class="list-group-item d-flex justify-content-between align-items-center">
${p.retailer}
<span class="badge bg-primary rounded-pill">R ${parseFloat(p.price).toFixed(2)}</span>
</li>
`).join('');
const productCard = `
<div class="col-md-6 col-lg-4">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">${product.name}</h5>
<ul class="list-group list-group-flush">
${pricesHtml}
</ul>
</div>
</div>
</div>
`;
resultsContainer.innerHTML += productCard;
});
}
}
}); });

70
db/migrate.php Normal file
View File

@ -0,0 +1,70 @@
<?php
// Simple, idempotent migration and seeding script.
require_once __DIR__ . '/config.php';
function run_migrations(PDO $pdo): void
{
$migrationsDir = __DIR__ . '/migrations';
if (!is_dir($migrationsDir)) {
echo "Migrations directory not found.\n";
return;
}
$files = glob($migrationsDir . '/*.sql');
sort($files);
foreach ($files as $file) {
echo "Running migration: " . basename($file) . "...\n";
$sql = file_get_contents($file);
if ($sql === false) {
echo "Failed to read migration file: " . basename($file) . "\n";
continue;
}
try {
$pdo->exec($sql);
echo "Success.\n";
} catch (PDOException $e) {
echo "Error running migration " . basename($file) . ": " . $e->getMessage() . "\n";
}
}
}
function seed_data(PDO $pdo): void
{
$products = [
['name' => 'Milk 1L', 'retailer' => 'Pick n Pay', 'price' => 25.99],
['name' => 'Milk 1L', 'retailer' => 'Checkers', 'price' => 24.99],
['name' => 'Bread (White)', 'retailer' => 'Pick n Pay', 'price' => 15.50],
['name' => 'Bread (White)', 'retailer' => 'Checkers', 'price' => 14.99],
['name' => 'Eggs (12 pack)', 'retailer' => 'Pick n Pay', 'price' => 32.00],
['name' => 'Eggs (12 pack)', 'retailer' => 'Checkers', 'price' => 31.50],
['name' => 'Cheese (250g)', 'retailer' => 'Pick n Pay', 'price' => 55.00],
['name' => 'Cheese (250g)', 'retailer' => 'Checkers', 'price' => 52.99],
];
$stmt = $pdo->prepare(
'INSERT INTO products (name, retailer, price) VALUES (:name, :retailer, :price)
ON DUPLICATE KEY UPDATE price = VALUES(price)'
);
echo "\nSeeding data...\n";
foreach ($products as $product) {
try {
$stmt->execute($product);
echo "Seeded/Updated: " . $product['name'] . " (" . $product['retailer'] . ")\n";
} catch (PDOException $e) {
echo "Error seeding " . $product['name'] . ": " . $e->getMessage() . "\n";
}
}
echo "Seeding complete.\n";
}
try {
$pdo = db();
run_migrations($pdo);
seed_data($pdo);
echo "\nDatabase setup complete!\n";
} catch (PDOException $e) {
die("Database connection failed: " . $e->getMessage() . "\n");
}

View File

@ -0,0 +1,8 @@
CREATE TABLE IF NOT EXISTS products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
retailer VARCHAR(255) NOT NULL,
price DECIMAL(10, 2) NOT NULL,
last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY `product_retailer` (`name`,`retailer`)
);

View File

@ -5,12 +5,28 @@
<div class="container"> <div class="container">
<h1>PricePal SA</h1> <h1>PricePal SA</h1>
<p>Your smart shopping companion for comparing prices across South African retailers.</p> <p>Your smart shopping companion for comparing prices across South African retailers.</p>
<a href="#" class="btn btn-light btn-lg"> <form id="searchForm" class="mt-4">
<i class="bi bi-google-play"></i> Download on Google Play <div class="input-group input-group-lg">
</a> <input type="search" id="searchInput" class="form-control" placeholder="Search for a product (e.g., Milk, Bread...)" aria-label="Search for a product">
<button class="btn btn-primary" type="submit" id="searchButton">Search</button>
</div>
</form>
</div> </div>
</header> </header>
<!-- Search Results Section -->
<section id="searchResults" class="section" style="display: none;">
<div class="container">
<div class="text-center mb-5">
<h2>Search Results</h2>
</div>
<div id="resultsContainer" class="row g-4"></div>
<div id="noResults" class="text-center" style="display: none;">
<p class="lead">No products found matching your search.</p>
</div>
</div>
</section>
<!-- Features Section --> <!-- Features Section -->
<section id="features" class="section"> <section id="features" class="section">
<div class="container"> <div class="container">

45
search.php Normal file
View File

@ -0,0 +1,45 @@
<?php
header('Content-Type: application/json');
require_once __DIR__ . '/db/config.php';
$query = $_GET['q'] ?? '';
if (empty($query)) {
echo json_encode([]);
exit;
}
try {
$pdo = db();
// Find all products matching the query
$stmt = $pdo->prepare(
'SELECT name, retailer, price FROM products WHERE name LIKE :query ORDER BY name, price'
);
$stmt->execute(['query' => '%' . $query . '%']);
$results = $stmt->fetchAll();
// Group prices by product name
$products = [];
foreach ($results as $row) {
if (!isset($products[$row['name']])) {
$products[$row['name']] = [
'name' => $row['name'],
'prices' => []
];
}
$products[$row['name']]['prices'][] = [
'retailer' => $row['retailer'],
'price' => $row['price']
];
}
// Return as a simple array of products
echo json_encode(array_values($products));
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['error' => 'Database error: ' . $e->getMessage()]);
}