Online Grocery Store IN HTML CSS AND JAVASCRIPT WITH PHP AND MY SQL

Project Introduction

A comprehensive online grocery store platform that allows customers to browse products, add items to cart, and place orders for delivery or pickup. This system includes product management, inventory tracking, order processing, payment integration, and delivery management. Perfect for local grocery stores, supermarkets, or specialty food shops looking to establish an online presence.


✨ Features

Customer Features

  • Product Browsing: Browse products by category, brand, or dietary preferences
  • Advanced Search: Search with filters (price range, brand, rating, dietary restrictions)
  • Product Details: View images, descriptions, nutritional info, and reviews
  • Shopping Cart: Add/remove items, adjust quantities, save for later
  • Wishlist: Save favorite products for future purchases
  • Multiple Addresses: Manage multiple delivery addresses
  • Order History: View past orders and reorder with one click
  • Real-time Inventory: See stock availability instantly
  • Subscription Service: Set up recurring orders for regular items
  • Store Locator: Find nearby store locations for pickup

Store Features

  • Inventory Management: Track stock levels, set low stock alerts
  • Product Management: Add/edit products with images, prices, descriptions
  • Category Management: Organize products into categories and subcategories
  • Discount & Promotions: Create coupons, flash sales, and bulk discounts
  • Order Management: View, process, and update orders
  • Delivery Management: Assign deliveries, track delivery status
  • Customer Management: View customer details and purchase history
  • Analytics Dashboard: Sales reports, popular products, revenue tracking

Shopping Features

  • Smart Cart: Auto-calculates totals, taxes, and shipping
  • Multiple Payment Options: Credit card, PayPal, cash on delivery
  • Delivery Slots: Choose preferred delivery time
  • Substitutions: Allow or disallow product substitutions
  • Special Instructions: Add notes for delivery or specific items
  • Loyalty Points: Earn points on purchases
  • Referral Program: Discounts for referring friends

Admin Features

  • Dashboard: Real-time sales and order statistics
  • Staff Management: Create roles for store managers, delivery staff
  • Supplier Management: Track product suppliers
  • Bulk Operations: Import/export products via CSV
  • Price Management: Bulk price updates, special pricing rules
  • Tax Configuration: Set tax rates by location
  • Shipping Zones: Configure shipping rates by area
  • Report Generation: Sales, inventory, customer reports

📁 File Structure

blog-website/
│
├── grocery/                            # Main grocery directory
│   ├── index.php                          # Store homepage
│   ├── shop.php                              # Product listing
│   ├── product.php                              # Single product view
│   ├── category.php                               # Category page
│   ├── cart.php                                       # Shopping cart
│   ├── checkout.php                                     # Checkout process
│   ├── order-confirmation.php                               # Order success
│   ├── track-order.php                                        # Order tracking
│   │
│   ├── account/                                      # Customer account
│   │   ├── dashboard.php                                 # Account dashboard
│   │   ├── orders.php                                       # Order history
│   │   ├── wishlist.php                                       # Wishlist
│   │   ├── addresses.php                                        # Saved addresses
│   │   ├── profile.php                                             # Profile settings
│   │   └── subscriptions.php                                          # Subscription management
│   │
│   ├── admin/                                        # Admin area
│   │   ├── dashboard.php                                 # Admin dashboard
│   │   ├── products.php                                     # Product management
│   │   ├── categories.php                                      # Category management
│   │   ├── orders.php                                             # Order management
│   │   ├── customers.php                                            # Customer management
│   │   ├── inventory.php                                              # Inventory tracking
│   │   ├── discounts.php                                                # Promotions & coupons
│   │   ├── suppliers.php                                                  # Supplier management
│   │   ├── delivery.php                                                     # Delivery management
│   │   ├── reports.php                                                        # Sales reports
│   │   └── settings.php                                                         # Store settings
│   │
│   ├── includes/                                    # Core includes
│   │   ├── grocery-config.php                          # Configuration
│   │   ├── grocery-functions.php                          # Core functions
│   │   ├── cart.php                                            # Cart functions
│   │   ├── checkout.php                                           # Checkout processing
│   │   ├── payment.php                                               # Payment integration
│   │   ├── inventory.php                                                # Inventory management
│   │   └── shipping.php                                                  # Shipping calculation
│   │
│   ├── api/                                          # API endpoints
│   │   ├── get-products.php                             # Product search API
│   │   ├── add-to-cart.php                                 # Add to cart
│   │   ├── update-cart.php                                    # Update cart
│   │   ├── remove-from-cart.php                                   # Remove from cart
│   │   ├── apply-coupon.php                                          # Apply discount
│   │   ├── calculate-shipping.php                                       # Shipping calculator
│   │   └── track-order.php                                                # Order tracking API
│   │
│   ├── css/                                          # Stylesheets
│   │   ├── grocery.css                                   # Main store styles
│   │   └── checkout.css                                     # Checkout styles
│   │
│   ├── js/                                           # JavaScript
│   │   ├── grocery.js                                    # Main store JS
│   │   ├── cart.js                                           # Cart functionality
│   │   ├── checkout.js                                         # Checkout validation
│   │   ├── product-filter.js                                       # Product filtering
│   │   └── admin.js                                                  # Admin panel JS
│   │
│   └── uploads/                                       # File uploads
│       ├── products/                                       # Product images
│       ├── categories/                                      # Category images
│       └── receipts/                                           # Order receipts
│
├── .env                                                  # Environment variables
├── composer.json                                            # Composer dependencies
│
└── database/
└── grocery.sql                                              # Database schema

🗄️ Database Schema (database/grocery.sql)

-- Create grocery database
CREATE DATABASE IF NOT EXISTS grocery_db;
USE grocery_db;
-- Users table
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL,
phone VARCHAR(20),
user_type ENUM('customer', 'store_manager', 'delivery_staff', 'admin') DEFAULT 'customer',
profile_image VARCHAR(500),
loyalty_points INT DEFAULT 0,
referred_by INT NULL,
is_verified BOOLEAN DEFAULT FALSE,
is_active BOOLEAN DEFAULT TRUE,
last_login TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_email (email),
INDEX idx_type (user_type),
INDEX idx_points (loyalty_points),
FOREIGN KEY (referred_by) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Addresses table
CREATE TABLE IF NOT EXISTS addresses (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
address_type ENUM('home', 'work', 'other') DEFAULT 'home',
recipient_name VARCHAR(255) NOT NULL,
recipient_phone VARCHAR(20) NOT NULL,
address_line1 VARCHAR(255) NOT NULL,
address_line2 VARCHAR(255),
city VARCHAR(100) NOT NULL,
state VARCHAR(100) NOT NULL,
postal_code VARCHAR(20) NOT NULL,
country VARCHAR(100) DEFAULT 'USA',
latitude DECIMAL(10,8),
longitude DECIMAL(11,8),
delivery_instructions TEXT,
is_default BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user (user_id),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Categories table
CREATE TABLE IF NOT EXISTS categories (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
slug VARCHAR(255) UNIQUE NOT NULL,
description TEXT,
parent_id INT NULL,
image VARCHAR(500),
icon VARCHAR(100),
sort_order INT DEFAULT 0,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_parent (parent_id),
INDEX idx_active (is_active),
FOREIGN KEY (parent_id) REFERENCES categories(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Brands table
CREATE TABLE IF NOT EXISTS brands (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL UNIQUE,
slug VARCHAR(255) UNIQUE NOT NULL,
logo VARCHAR(500),
description TEXT,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Products table
CREATE TABLE IF NOT EXISTS products (
id INT AUTO_INCREMENT PRIMARY KEY,
sku VARCHAR(100) UNIQUE NOT NULL,
name VARCHAR(500) NOT NULL,
slug VARCHAR(500) UNIQUE NOT NULL,
description TEXT,
short_description TEXT,
category_id INT NOT NULL,
brand_id INT NULL,
unit_type ENUM('piece', 'kg', 'g', 'l', 'ml', 'dozen', 'pack') DEFAULT 'piece',
unit_value DECIMAL(10,2),
price DECIMAL(10,2) NOT NULL,
compare_price DECIMAL(10,2),
cost_price DECIMAL(10,2),
tax_rate DECIMAL(5,2) DEFAULT 0,
stock_quantity INT NOT NULL DEFAULT 0,
low_stock_threshold INT DEFAULT 5,
allow_backorder BOOLEAN DEFAULT FALSE,
images JSON,
dietary_info JSON, -- vegetarian, vegan, gluten-free, etc.
nutritional_info JSON,
ingredients TEXT,
country_of_origin VARCHAR(100),
is_featured BOOLEAN DEFAULT FALSE,
is_active BOOLEAN DEFAULT TRUE,
views INT DEFAULT 0,
sold_count INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_category (category_id),
INDEX idx_brand (brand_id),
INDEX idx_price (price),
INDEX idx_active (is_active),
INDEX idx_featured (is_featured),
FULLTEXT INDEX idx_search (name, description, ingredients),
FOREIGN KEY (category_id) REFERENCES categories(id),
FOREIGN KEY (brand_id) REFERENCES brands(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Product variants (for different sizes/packaging)
CREATE TABLE IF NOT EXISTS product_variants (
id INT AUTO_INCREMENT PRIMARY KEY,
product_id INT NOT NULL,
sku VARCHAR(100) UNIQUE NOT NULL,
name VARCHAR(255) NOT NULL,
unit_value DECIMAL(10,2),
unit_type ENUM('piece', 'kg', 'g', 'l', 'ml', 'dozen', 'pack') DEFAULT 'piece',
price DECIMAL(10,2) NOT NULL,
compare_price DECIMAL(10,2),
stock_quantity INT NOT NULL DEFAULT 0,
images JSON,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_product (product_id),
FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Product reviews
CREATE TABLE IF NOT EXISTS product_reviews (
id INT AUTO_INCREMENT PRIMARY KEY,
product_id INT NOT NULL,
user_id INT NOT NULL,
order_id INT NULL,
rating TINYINT NOT NULL CHECK (rating >= 1 AND rating <= 5),
title VARCHAR(255),
review TEXT,
images JSON,
is_verified_purchase BOOLEAN DEFAULT FALSE,
status ENUM('pending', 'approved', 'rejected') DEFAULT 'pending',
helpful_count INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_product (product_id),
INDEX idx_user (user_id),
INDEX idx_status (status),
FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Discounts/Coupons
CREATE TABLE IF NOT EXISTS discounts (
id INT AUTO_INCREMENT PRIMARY KEY,
code VARCHAR(50) UNIQUE NOT NULL,
type ENUM('percentage', 'fixed', 'buy_x_get_y', 'free_shipping') NOT NULL,
value DECIMAL(10,2),
min_order_amount DECIMAL(10,2),
max_discount_amount DECIMAL(10,2),
applies_to ENUM('all', 'category', 'product', 'brand') DEFAULT 'all',
applies_to_ids JSON,
usage_limit INT,
usage_per_customer INT,
used_count INT DEFAULT 0,
start_date TIMESTAMP NULL,
end_date TIMESTAMP NULL,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_code (code),
INDEX idx_dates (start_date, end_date),
INDEX idx_active (is_active)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Shopping cart
CREATE TABLE IF NOT EXISTS cart (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NULL,
session_id VARCHAR(255),
product_id INT NOT NULL,
variant_id INT NULL,
quantity INT NOT NULL DEFAULT 1,
price DECIMAL(10,2) NOT NULL,
added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_user (user_id),
INDEX idx_session (session_id),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE,
FOREIGN KEY (variant_id) REFERENCES product_variants(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Wishlist
CREATE TABLE IF NOT EXISTS wishlist (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
product_id INT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY unique_wishlist (user_id, product_id),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Orders table
CREATE TABLE IF NOT EXISTS orders (
id INT AUTO_INCREMENT PRIMARY KEY,
order_number VARCHAR(50) UNIQUE NOT NULL,
user_id INT NOT NULL,
address_id INT NOT NULL,
subtotal DECIMAL(10,2) NOT NULL,
discount_amount DECIMAL(10,2) DEFAULT 0,
tax_amount DECIMAL(10,2) DEFAULT 0,
shipping_amount DECIMAL(10,2) DEFAULT 0,
total_amount DECIMAL(10,2) NOT NULL,
payment_method ENUM('credit_card', 'paypal', 'cash_on_delivery', 'bank_transfer') NOT NULL,
payment_status ENUM('pending', 'paid', 'failed', 'refunded') DEFAULT 'pending',
transaction_id VARCHAR(255),
order_status ENUM('pending', 'confirmed', 'processing', 'ready_for_pickup', 'out_for_delivery', 'delivered', 'cancelled', 'refunded') DEFAULT 'pending',
delivery_type ENUM('delivery', 'pickup') DEFAULT 'delivery',
delivery_slot DATETIME,
estimated_delivery DATETIME,
actual_delivery DATETIME,
delivery_notes TEXT,
special_instructions TEXT,
allow_substitutions BOOLEAN DEFAULT FALSE,
loyalty_points_earned INT DEFAULT 0,
loyalty_points_used INT DEFAULT 0,
coupon_code VARCHAR(50),
ip_address VARCHAR(45),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_user (user_id),
INDEX idx_status (order_status),
INDEX idx_payment (payment_status),
INDEX idx_created (created_at),
INDEX idx_number (order_number),
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (address_id) REFERENCES addresses(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Order items
CREATE TABLE IF NOT EXISTS order_items (
id INT AUTO_INCREMENT PRIMARY KEY,
order_id INT NOT NULL,
product_id INT NOT NULL,
variant_id INT NULL,
product_name VARCHAR(500) NOT NULL,
quantity INT NOT NULL,
price DECIMAL(10,2) NOT NULL,
subtotal DECIMAL(10,2) NOT NULL,
discount_amount DECIMAL(10,2) DEFAULT 0,
tax_amount DECIMAL(10,2) DEFAULT 0,
total_amount DECIMAL(10,2) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_order (order_id),
FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE,
FOREIGN KEY (product_id) REFERENCES products(id),
FOREIGN KEY (variant_id) REFERENCES product_variants(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Inventory transactions
CREATE TABLE IF NOT EXISTS inventory_transactions (
id INT AUTO_INCREMENT PRIMARY KEY,
product_id INT NOT NULL,
variant_id INT NULL,
quantity_change INT NOT NULL,
type ENUM('purchase', 'sale', 'return', 'adjustment', 'damage') NOT NULL,
reference_id INT, -- order_id, purchase_order_id, etc.
notes TEXT,
created_by INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_product (product_id),
FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE,
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Delivery tracking
CREATE TABLE IF NOT EXISTS deliveries (
id INT AUTO_INCREMENT PRIMARY KEY,
order_id INT NOT NULL UNIQUE,
delivery_staff_id INT,
assigned_at TIMESTAMP NULL,
picked_up_at TIMESTAMP NULL,
out_for_delivery_at TIMESTAMP NULL,
delivered_at TIMESTAMP NULL,
delivery_photo VARCHAR(500),
recipient_name VARCHAR(255),
recipient_signature VARCHAR(500),
delivery_status ENUM('assigned', 'picked_up', 'out_for_delivery', 'delivered', 'failed') DEFAULT 'assigned',
failed_reason TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_staff (delivery_staff_id),
INDEX idx_status (delivery_status),
FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE,
FOREIGN KEY (delivery_staff_id) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Suppliers
CREATE TABLE IF NOT EXISTS suppliers (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
contact_person VARCHAR(255),
email VARCHAR(255),
phone VARCHAR(20),
address TEXT,
tax_id VARCHAR(100),
payment_terms VARCHAR(100),
lead_time_days INT,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Purchase orders (to suppliers)
CREATE TABLE IF NOT EXISTS purchase_orders (
id INT AUTO_INCREMENT PRIMARY KEY,
po_number VARCHAR(50) UNIQUE NOT NULL,
supplier_id INT NOT NULL,
order_date DATE NOT NULL,
expected_delivery DATE,
actual_delivery DATE,
status ENUM('draft', 'sent', 'confirmed', 'partially_received', 'received', 'cancelled') DEFAULT 'draft',
subtotal DECIMAL(10,2) NOT NULL,
tax_amount DECIMAL(10,2) DEFAULT 0,
shipping_amount DECIMAL(10,2) DEFAULT 0,
total_amount DECIMAL(10,2) NOT NULL,
notes TEXT,
created_by INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_supplier (supplier_id),
INDEX idx_status (status),
FOREIGN KEY (supplier_id) REFERENCES suppliers(id),
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Purchase order items
CREATE TABLE IF NOT EXISTS purchase_order_items (
id INT AUTO_INCREMENT PRIMARY KEY,
purchase_order_id INT NOT NULL,
product_id INT NOT NULL,
variant_id INT NULL,
quantity INT NOT NULL,
received_quantity INT DEFAULT 0,
unit_price DECIMAL(10,2) NOT NULL,
total_price DECIMAL(10,2) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_po (purchase_order_id),
FOREIGN KEY (purchase_order_id) REFERENCES purchase_orders(id) ON DELETE CASCADE,
FOREIGN KEY (product_id) REFERENCES products(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Store settings
CREATE TABLE IF NOT EXISTS store_settings (
id INT AUTO_INCREMENT PRIMARY KEY,
setting_key VARCHAR(255) UNIQUE NOT NULL,
setting_value TEXT,
setting_type ENUM('text', 'number', 'boolean', 'json') DEFAULT 'text',
description TEXT,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Delivery slots
CREATE TABLE IF NOT EXISTS delivery_slots (
id INT AUTO_INCREMENT PRIMARY KEY,
day_of_week TINYINT, -- 0=Sunday, 1=Monday, etc.
start_time TIME NOT NULL,
end_time TIME NOT NULL,
max_orders INT DEFAULT 10,
cutoff_time TIME, -- Last order time for this slot
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Subscriptions (recurring orders)
CREATE TABLE IF NOT EXISTS subscriptions (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
address_id INT NOT NULL,
frequency ENUM('weekly', 'biweekly', 'monthly') NOT NULL,
day_of_week TINYINT,
day_of_month TINYINT,
next_delivery_date DATE NOT NULL,
items JSON NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_user (user_id),
INDEX idx_next_delivery (next_delivery_date),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (address_id) REFERENCES addresses(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Insert default settings
INSERT INTO store_settings (setting_key, setting_value, setting_type, description) VALUES
('store_name', 'My Online Grocery', 'text', 'Store name'),
('store_email', '[email protected]', 'text', 'Store email'),
('store_phone', '+1234567890', 'text', 'Store phone number'),
('currency', 'USD', 'text', 'Default currency'),
('tax_rate', '8.5', 'number', 'Default tax rate percentage'),
('delivery_charge', '5.00', 'number', 'Standard delivery charge'),
('free_delivery_min', '50.00', 'number', 'Minimum order for free delivery'),
('min_order_amount', '10.00', 'number', 'Minimum order amount'),
('max_cart_items', '50', 'number', 'Maximum items in cart'),
('enable_reviews', 'true', 'boolean', 'Enable product reviews'),
('require_login_checkout', 'false', 'boolean', 'Require login for checkout'),
('enable_subscriptions', 'true', 'boolean', 'Enable subscription service'),
('loyalty_points_rate', '10', 'number', 'Points per dollar spent');
-- Insert sample categories
INSERT INTO categories (name, slug, description, icon) VALUES
('Fruits & Vegetables', 'fruits-vegetables', 'Fresh fruits and vegetables', '🍎'),
('Dairy & Eggs', 'dairy-eggs', 'Milk, cheese, eggs and more', '🥛'),
('Meat & Seafood', 'meat-seafood', 'Fresh meat and seafood', '🥩'),
('Bakery', 'bakery', 'Fresh bread and baked goods', '🥖'),
('Beverages', 'beverages', 'Soft drinks, juices, water', '🥤'),
('Snacks', 'snacks', 'Chips, cookies, candies', '🍪'),
('Frozen Foods', 'frozen-foods', 'Frozen meals and ice cream', '❄️'),
('Household', 'household', 'Cleaning supplies and paper products', '🧹');
-- Insert sample brands
INSERT INTO brands (name, slug) VALUES
('Organic Farms', 'organic-farms'),
('Dairy Best', 'dairy-best'),
('Fresh Catch', 'fresh-catch'),
('Baker\'s Delight', 'bakers-delight'),
('Coca-Cola', 'coca-cola'),
('PepsiCo', 'pepsico');
-- Insert sample products
INSERT INTO products (sku, name, slug, category_id, unit_type, price, stock_quantity, description) VALUES
('APP001', 'Red Apples', 'red-apples', 1, 'kg', 3.99, 100, 'Fresh red apples from local farms'),
('BAN001', 'Bananas', 'bananas', 1, 'kg', 2.49, 150, 'Ripe yellow bananas'),
('MIL001', 'Whole Milk', 'whole-milk', 2, 'l', 4.99, 50, 'Fresh whole milk'),
('EGG001', 'Grade A Eggs', 'grade-a-eggs', 2, 'dozen', 5.99, 60, 'Large brown eggs'),
('CHI001', 'Chicken Breast', 'chicken-breast', 3, 'kg', 12.99, 30, 'Boneless skinless chicken breast'),
('BRE001', 'Whole Wheat Bread', 'whole-wheat-bread', 4, 'piece', 3.99, 40, 'Fresh baked whole wheat bread'),
('COC001', 'Coca-Cola 2L', 'coca-cola-2l', 5, 'piece', 2.99, 100, 'Classic Coca-Cola 2 liter'),
('POT001', 'Potato Chips', 'potato-chips', 6, 'piece', 4.99, 80, 'Classic salted potato chips'),
('ICE001', 'Vanilla Ice Cream', 'vanilla-ice-cream', 7, 'l', 6.99, 25, 'Creamy vanilla ice cream'),
('PAP001', 'Paper Towels', 'paper-towels', 8, 'pack', 8.99, 60, 'Strong absorbent paper towels');

🔧 Core Functions

1. includes/grocery-config.php

<?php
/**
* Grocery Store Configuration
*/
require_once __DIR__ . '/../../vendor/autoload.php';
use Dotenv\Dotenv;
// Load environment variables
$dotenv = Dotenv::createImmutable(__DIR__ . '/../../');
$dotenv->load();
// Database configuration
define('DB_HOST', $_ENV['DB_HOST']);
define('DB_USER', $_ENV['DB_USER']);
define('DB_PASS', $_ENV['DB_PASS']);
define('DB_NAME', 'grocery_db');
// Store settings
define('STORE_NAME', $_ENV['STORE_NAME'] ?? 'My Online Grocery');
define('STORE_EMAIL', $_ENV['STORE_EMAIL'] ?? '[email protected]');
define('STORE_PHONE', $_ENV['STORE_PHONE'] ?? '+1234567890');
define('CURRENCY', $_ENV['CURRENCY'] ?? 'USD');
define('CURRENCY_SYMBOL', $_ENV['CURRENCY_SYMBOL'] ?? '$');
// Tax & Shipping
define('DEFAULT_TAX_RATE', $_ENV['DEFAULT_TAX_RATE'] ?? 8.5);
define('DELIVERY_CHARGE', $_ENV['DELIVERY_CHARGE'] ?? 5.00);
define('FREE_DELIVERY_MIN', $_ENV['FREE_DELIVERY_MIN'] ?? 50.00);
define('MIN_ORDER_AMOUNT', $_ENV['MIN_ORDER_AMOUNT'] ?? 10.00);
// Inventory settings
define('LOW_STOCK_THRESHOLD', $_ENV['LOW_STOCK_THRESHOLD'] ?? 5);
define('MAX_CART_ITEMS', $_ENV['MAX_CART_ITEMS'] ?? 50);
// Loyalty points
define('LOYALTY_POINTS_RATE', $_ENV['LOYALTY_POINTS_RATE'] ?? 10); // Points per dollar
define('POINTS_TO_CURRENCY', $_ENV['POINTS_TO_CURRENCY'] ?? 100); // 100 points = $1
// Pagination
define('PRODUCTS_PER_PAGE', $_ENV['PRODUCTS_PER_PAGE'] ?? 24);
define('ORDERS_PER_PAGE', $_ENV['ORDERS_PER_PAGE'] ?? 20);
// Start session
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Database connection
function getDB() {
static $pdo = null;
if ($pdo === null) {
try {
$dsn = "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4";
$pdo = new PDO($dsn, DB_USER, DB_PASS, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false
]);
} catch (PDOException $e) {
error_log("Database connection failed: " . $e->getMessage());
die("Database connection failed. Please try again later.");
}
}
return $pdo;
}
// Get current user
function getCurrentUser() {
if (isset($_SESSION['user_id'])) {
$pdo = getDB();
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$_SESSION['user_id']]);
return $stmt->fetch();
}
return null;
}
// Check if user is logged in
function isLoggedIn() {
return isset($_SESSION['user_id']);
}
// Require login
function requireLogin() {
if (!isLoggedIn()) {
$_SESSION['redirect_after_login'] = $_SERVER['REQUEST_URI'];
header('Location: /blog-website/login.php');
exit;
}
}
// Format price
function formatPrice($price) {
return CURRENCY_SYMBOL . number_format($price, 2);
}
// Generate unique order number
function generateOrderNumber() {
$pdo = getDB();
$prefix = 'ORD';
$year = date('Y');
$month = date('m');
// Get last order number for this month
$stmt = $pdo->prepare("
SELECT order_number FROM orders 
WHERE order_number LIKE ? 
ORDER BY id DESC LIMIT 1
");
$stmt->execute([$prefix . $year . $month . '%']);
$last = $stmt->fetch();
if ($last) {
$lastNum = intval(substr($last['order_number'], -4));
$newNum = $lastNum + 1;
} else {
$newNum = 1;
}
return $prefix . $year . $month . str_pad($newNum, 4, '0', STR_PAD_LEFT);
}
// Get cart count
function getCartCount() {
if (isset($_SESSION['user_id'])) {
$pdo = getDB();
$stmt = $pdo->prepare("SELECT SUM(quantity) FROM cart WHERE user_id = ?");
$stmt->execute([$_SESSION['user_id']]);
return (int)$stmt->fetchColumn();
} else {
$cart = $_SESSION['cart'] ?? [];
return array_sum(array_column($cart, 'quantity'));
}
}
// Calculate cart total
function calculateCartTotal() {
$pdo = getDB();
$total = 0;
if (isset($_SESSION['user_id'])) {
$stmt = $pdo->prepare("
SELECT SUM(c.quantity * c.price) as total 
FROM cart c 
WHERE c.user_id = ?
");
$stmt->execute([$_SESSION['user_id']]);
$total = (float)$stmt->fetchColumn();
} else {
$cart = $_SESSION['cart'] ?? [];
foreach ($cart as $item) {
$total += $item['quantity'] * $item['price'];
}
}
return $total;
}
// Get store setting
function getStoreSetting($key, $default = null) {
static $settings = null;
if ($settings === null) {
$pdo = getDB();
$stmt = $pdo->query("SELECT setting_key, setting_value FROM store_settings");
while ($row = $stmt->fetch()) {
$settings[$row['setting_key']] = $row['setting_value'];
}
}
return $settings[$key] ?? $default;
}
// Update inventory after order
function updateInventoryAfterOrder($orderId) {
$pdo = getDB();
// Get order items
$stmt = $pdo->prepare("SELECT * FROM order_items WHERE order_id = ?");
$stmt->execute([$orderId]);
$items = $stmt->fetchAll();
foreach ($items as $item) {
// Update product stock
$pdo->prepare("
UPDATE products 
SET stock_quantity = stock_quantity - ?,
sold_count = sold_count + ?
WHERE id = ?
")->execute([$item['quantity'], $item['quantity'], $item['product_id']]);
// Record inventory transaction
$pdo->prepare("
INSERT INTO inventory_transactions 
(product_id, quantity_change, type, reference_id, notes)
VALUES (?, ?, 'sale', ?, 'Order placed')
")->execute([$item['product_id'], -$item['quantity'], $orderId]);
// Check low stock alert
$product = $pdo->prepare("SELECT stock_quantity, low_stock_threshold FROM products WHERE id = ?");
$product->execute([$item['product_id']]);
$prod = $product->fetch();
if ($prod['stock_quantity'] <= $prod['low_stock_threshold']) {
// Send low stock notification
sendLowStockAlert($item['product_id'], $prod['stock_quantity']);
}
}
}
// Send low stock alert (implement email/notification)
function sendLowStockAlert($productId, $currentStock) {
// Get product details
$pdo = getDB();
$stmt = $pdo->prepare("SELECT name FROM products WHERE id = ?");
$stmt->execute([$productId]);
$product = $stmt->fetch();
// Log alert (implement actual notification)
error_log("LOW STOCK ALERT: {$product['name']} (ID: $productId) has only $currentStock units left");
}

2. grocery/index.php (Store Homepage)

<?php
require_once '../includes/config.php';
require_once 'includes/grocery-config.php';
require_once 'includes/grocery-functions.php';
$page_title = STORE_NAME . ' - Fresh Groceries Delivered';
$pdo = getDB();
// Get featured products
$featured = $pdo->query("
SELECT p.*, c.name as category_name, c.slug as category_slug
FROM products p
JOIN categories c ON p.category_id = c.id
WHERE p.is_featured = 1 AND p.is_active = 1
ORDER BY p.created_at DESC
LIMIT 8
")->fetchAll();
// Get categories with product counts
$categories = $pdo->query("
SELECT c.*, COUNT(p.id) as product_count
FROM categories c
LEFT JOIN products p ON c.id = p.category_id AND p.is_active = 1
WHERE c.is_active = 1
GROUP BY c.id
ORDER BY c.sort_order
LIMIT 12
")->fetchAll();
// Get deals (products with compare price)
$deals = $pdo->query("
SELECT * FROM products 
WHERE compare_price IS NOT NULL 
AND compare_price > price 
AND is_active = 1
ORDER BY (compare_price - price) DESC
LIMIT 6
")->fetchAll();
// Get recent products
$recent = $pdo->query("
SELECT * FROM products 
WHERE is_active = 1 
ORDER BY created_at DESC 
LIMIT 8
")->fetchAll();
include '../includes/header.php';
?>
<link rel="stylesheet" href="css/grocery.css">
<div class="grocery-store">
<!-- Hero Banner -->
<section class="hero-banner">
<div class="hero-content">
<h1>Fresh Groceries Delivered to Your Doorstep</h1>
<p>Shop from thousands of products with free delivery on orders over <?php echo formatPrice(FREE_DELIVERY_MIN); ?></p>
<div class="search-box">
<form action="shop.php" method="GET" class="search-form">
<input type="text" 
name="search" 
placeholder="Search for products, brands, or categories..."
autocomplete="off"
id="searchInput">
<button type="submit">Search</button>
</form>
<div id="searchSuggestions" class="search-suggestions" style="display: none;"></div>
</div>
<div class="delivery-info">
<span>🚚 Delivery in 2 hours</span>
<span>📍 Free delivery available</span>
<span>💳 Secure payment</span>
</div>
</div>
</section>
<!-- Categories Grid -->
<section class="categories-section">
<h2>Shop by Category</h2>
<div class="categories-grid">
<?php foreach ($categories as $category): ?>
<a href="category.php?slug=<?php echo $category['slug']; ?>" class="category-card">
<div class="category-icon"><?php echo $category['icon'] ?? '🛒'; ?></div>
<h3><?php echo htmlspecialchars($category['name']); ?></h3>
<span class="product-count"><?php echo $category['product_count']; ?> items</span>
</a>
<?php endforeach; ?>
</div>
</section>
<!-- Featured Products -->
<?php if (!empty($featured)): ?>
<section class="products-section">
<div class="section-header">
<h2>Featured Products</h2>
<a href="shop.php?featured=1" class="view-all">View All →</a>
</div>
<div class="products-grid">
<?php foreach ($featured as $product): ?>
<div class="product-card">
<div class="product-image">
<a href="product.php?slug=<?php echo $product['slug']; ?>">
<?php 
$images = json_decode($product['images'], true);
$firstImage = $images[0] ?? '/blog-website/assets/product-placeholder.jpg';
?>
<img src="<?php echo htmlspecialchars($firstImage); ?>" 
alt="<?php echo htmlspecialchars($product['name']); ?>"
loading="lazy">
</a>
<?php if ($product['compare_price']): ?>
<span class="discount-badge">
-<?php echo round((($product['compare_price'] - $product['price']) / $product['compare_price']) * 100); ?>%
</span>
<?php endif; ?>
<button class="wishlist-btn" data-product="<?php echo $product['id']; ?>">
♡
</button>
</div>
<div class="product-info">
<a href="product.php?slug=<?php echo $product['slug']; ?>" class="product-name">
<?php echo htmlspecialchars($product['name']); ?>
</a>
<div class="product-category">
<?php echo htmlspecialchars($product['category_name']); ?>
</div>
<div class="product-price">
<?php if ($product['compare_price']): ?>
<span class="original-price"><?php echo formatPrice($product['compare_price']); ?></span>
<?php endif; ?>
<span class="current-price"><?php echo formatPrice($product['price']); ?></span>
</div>
<div class="product-actions">
<div class="quantity-selector" style="display: none;">
<button class="qty-down">-</button>
<input type="number" value="1" min="1" max="<?php echo $product['stock_quantity']; ?>" class="qty-input">
<button class="qty-up">+</button>
</div>
<button class="add-to-cart" data-product="<?php echo $product['id']; ?>">
Add to Cart
</button>
</div>
<?php if ($product['stock_quantity'] <= $product['low_stock_threshold'] && $product['stock_quantity'] > 0): ?>
<div class="low-stock">Only <?php echo $product['stock_quantity']; ?> left!</div>
<?php elseif ($product['stock_quantity'] == 0): ?>
<div class="out-of-stock">Out of Stock</div>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
</div>
</section>
<?php endif; ?>
<!-- Special Deals -->
<?php if (!empty($deals)): ?>
<section class="deals-section">
<h2>Today's Deals</h2>
<div class="deals-grid">
<?php foreach ($deals as $deal): ?>
<div class="deal-card">
<?php 
$images = json_decode($deal['images'], true);
$firstImage = $images[0] ?? '/blog-website/assets/product-placeholder.jpg';
?>
<img src="<?php echo htmlspecialchars($firstImage); ?>" 
alt="<?php echo htmlspecialchars($deal['name']); ?>">
<div class="deal-info">
<h4><?php echo htmlspecialchars($deal['name']); ?></h4>
<div class="deal-price">
<span class="old-price"><?php echo formatPrice($deal['compare_price']); ?></span>
<span class="new-price"><?php echo formatPrice($deal['price']); ?></span>
</div>
<a href="product.php?slug=<?php echo $deal['slug']; ?>" class="btn-view">View Deal</a>
</div>
</div>
<?php endforeach; ?>
</div>
</section>
<?php endif; ?>
<!-- Why Choose Us -->
<section class="features-section">
<h2>Why Choose Us</h2>
<div class="features-grid">
<div class="feature">
<div class="feature-icon">🚚</div>
<h3>Fast Delivery</h3>
<p>Get your groceries delivered in as little as 2 hours</p>
</div>
<div class="feature">
<div class="feature-icon">🌱</div>
<h3>Fresh Products</h3>
<p>We source directly from local farms and suppliers</p>
</div>
<div class="feature">
<div class="feature-icon">💳</div>
<h3>Secure Payment</h3>
<p>Multiple payment options with 100% security</p>
</div>
<div class="feature">
<div class="feature-icon">🎁</div>
<h3>Loyalty Points</h3>
<p>Earn points on every purchase and save more</p>
</div>
</div>
</section>
<!-- Recent Products -->
<?php if (!empty($recent)): ?>
<section class="recent-section">
<h2>New Arrivals</h2>
<div class="products-grid">
<?php foreach ($recent as $product): ?>
<div class="product-card">
<div class="product-image">
<a href="product.php?slug=<?php echo $product['slug']; ?>">
<?php 
$images = json_decode($product['images'], true);
$firstImage = $images[0] ?? '/blog-website/assets/product-placeholder.jpg';
?>
<img src="<?php echo htmlspecialchars($firstImage); ?>" 
alt="<?php echo htmlspecialchars($product['name']); ?>">
</a>
<button class="wishlist-btn" data-product="<?php echo $product['id']; ?>">♡</button>
</div>
<div class="product-info">
<a href="product.php?slug=<?php echo $product['slug']; ?>" class="product-name">
<?php echo htmlspecialchars($product['name']); ?>
</a>
<div class="product-price">
<?php echo formatPrice($product['price']); ?>
</div>
<button class="add-to-cart" data-product="<?php echo $product['id']; ?>">
Add to Cart
</button>
</div>
</div>
<?php endforeach; ?>
</div>
</section>
<?php endif; ?>
<!-- Newsletter -->
<section class="newsletter-section">
<div class="newsletter-content">
<h2>Subscribe to Our Newsletter</h2>
<p>Get updates on new products, special offers, and grocery tips</p>
<form class="newsletter-form" id="newsletterForm">
<input type="email" placeholder="Your email address" required>
<button type="submit">Subscribe</button>
</form>
</div>
</section>
</div>
<!-- Quick view modal -->
<div id="quickViewModal" class="modal" style="display: none;">
<div class="modal-content">
<span class="close">&times;</span>
<div id="quickViewContent"></div>
</div>
</div>
<script src="js/grocery.js"></script>
<style>
.grocery-store {
max-width: 1400px;
margin: 0 auto;
padding: 0 1rem;
}
/* Hero Banner */
.hero-banner {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 4rem 2rem;
border-radius: 10px;
margin-bottom: 3rem;
text-align: center;
}
.hero-content h1 {
font-size: 2.5rem;
margin-bottom: 1rem;
}
.hero-content p {
font-size: 1.2rem;
margin-bottom: 2rem;
opacity: 0.9;
}
.search-box {
max-width: 600px;
margin: 0 auto 2rem;
position: relative;
}
.search-form {
display: flex;
gap: 0.5rem;
}
.search-form input {
flex: 1;
padding: 1rem;
border: none;
border-radius: 5px;
font-size: 1rem;
}
.search-form button {
padding: 1rem 2rem;
background: #ff6b6b;
color: white;
border: none;
border-radius: 5px;
font-size: 1rem;
cursor: pointer;
transition: opacity 0.3s;
}
.search-form button:hover {
opacity: 0.9;
}
.search-suggestions {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: white;
border-radius: 5px;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
z-index: 100;
max-height: 300px;
overflow-y: auto;
}
.delivery-info {
display: flex;
justify-content: center;
gap: 2rem;
font-size: 1rem;
}
.delivery-info span {
display: flex;
align-items: center;
gap: 0.5rem;
}
/* Categories */
.categories-section {
margin-bottom: 4rem;
}
.categories-section h2 {
text-align: center;
font-size: 2rem;
color: #333;
margin-bottom: 2rem;
}
.categories-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1.5rem;
}
.category-card {
background: white;
border-radius: 10px;
padding: 2rem 1rem;
text-align: center;
text-decoration: none;
color: #333;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
transition: transform 0.3s;
}
.category-card:hover {
transform: translateY(-5px);
}
.category-icon {
font-size: 3rem;
margin-bottom: 1rem;
}
.category-card h3 {
margin-bottom: 0.5rem;
}
.product-count {
color: #888;
font-size: 0.9rem;
}
/* Products Section */
.products-section, .recent-section {
margin-bottom: 4rem;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
}
.section-header h2 {
font-size: 1.8rem;
color: #333;
}
.view-all {
color: #667eea;
text-decoration: none;
}
.products-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 2rem;
}
.product-card {
background: white;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
transition: transform 0.3s;
position: relative;
}
.product-card:hover {
transform: translateY(-5px);
}
.product-image {
position: relative;
aspect-ratio: 1 / 1;
overflow: hidden;
}
.product-image img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s;
}
.product-card:hover .product-image img {
transform: scale(1.05);
}
.discount-badge {
position: absolute;
top: 10px;
left: 10px;
background: #ff6b6b;
color: white;
padding: 0.3rem 0.8rem;
border-radius: 20px;
font-size: 0.9rem;
font-weight: 500;
}
.wishlist-btn {
position: absolute;
top: 10px;
right: 10px;
background: white;
border: none;
width: 35px;
height: 35px;
border-radius: 50%;
font-size: 1.2rem;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
transition: all 0.3s;
}
.wishlist-btn:hover {
background: #ff6b6b;
color: white;
}
.product-info {
padding: 1.5rem;
}
.product-name {
display: block;
color: #333;
text-decoration: none;
font-weight: 500;
margin-bottom: 0.3rem;
}
.product-name:hover {
color: #667eea;
}
.product-category {
color: #888;
font-size: 0.9rem;
margin-bottom: 1rem;
}
.product-price {
margin-bottom: 1rem;
}
.original-price {
color: #999;
text-decoration: line-through;
font-size: 0.9rem;
margin-right: 0.5rem;
}
.current-price {
color: #333;
font-weight: bold;
font-size: 1.2rem;
}
.product-actions {
display: flex;
gap: 0.5rem;
}
.quantity-selector {
display: flex;
align-items: center;
border: 1px solid #ddd;
border-radius: 5px;
overflow: hidden;
}
.qty-down, .qty-up {
width: 30px;
height: 40px;
background: #f8f9fa;
border: none;
cursor: pointer;
font-size: 1.2rem;
}
.qty-input {
width: 50px;
height: 40px;
border: none;
border-left: 1px solid #ddd;
border-right: 1px solid #ddd;
text-align: center;
}
.add-to-cart {
flex: 1;
padding: 0.8rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
transition: opacity 0.3s;
}
.add-to-cart:hover {
opacity: 0.9;
}
.low-stock {
margin-top: 0.5rem;
color: #ff6b6b;
font-size: 0.85rem;
}
.out-of-stock {
margin-top: 0.5rem;
color: #999;
font-size: 0.85rem;
font-style: italic;
}
/* Deals Section */
.deals-section {
margin-bottom: 4rem;
}
.deals-section h2 {
text-align: center;
font-size: 2rem;
color: #333;
margin-bottom: 2rem;
}
.deals-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
}
.deal-card {
background: linear-gradient(135deg, #ff6b6b 0%, #ff8e8e 100%);
border-radius: 10px;
overflow: hidden;
display: flex;
color: white;
}
.deal-card img {
width: 120px;
height: 120px;
object-fit: cover;
}
.deal-info {
padding: 1.5rem;
flex: 1;
}
.deal-info h4 {
margin-bottom: 0.5rem;
}
.deal-price {
margin-bottom: 1rem;
}
.old-price {
text-decoration: line-through;
opacity: 0.7;
margin-right: 0.5rem;
}
.new-price {
font-weight: bold;
font-size: 1.2rem;
}
.btn-view {
display: inline-block;
padding: 0.5rem 1rem;
background: white;
color: #ff6b6b;
text-decoration: none;
border-radius: 5px;
font-weight: 500;
}
/* Features Section */
.features-section {
background: #f8f9fa;
padding: 4rem 2rem;
border-radius: 10px;
margin-bottom: 4rem;
}
.features-section h2 {
text-align: center;
font-size: 2rem;
color: #333;
margin-bottom: 2rem;
}
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
}
.feature {
text-align: center;
}
.feature-icon {
font-size: 3rem;
margin-bottom: 1rem;
}
.feature h3 {
margin-bottom: 0.5rem;
color: #333;
}
.feature p {
color: #666;
line-height: 1.6;
}
/* Newsletter */
.newsletter-section {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 10px;
padding: 4rem 2rem;
margin-bottom: 2rem;
text-align: center;
color: white;
}
.newsletter-content h2 {
font-size: 2rem;
margin-bottom: 1rem;
}
.newsletter-content p {
margin-bottom: 2rem;
opacity: 0.9;
}
.newsletter-form {
display: flex;
max-width: 500px;
margin: 0 auto;
gap: 0.5rem;
}
.newsletter-form input {
flex: 1;
padding: 1rem;
border: none;
border-radius: 5px;
font-size: 1rem;
}
.newsletter-form button {
padding: 1rem 2rem;
background: #ff6b6b;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
transition: opacity 0.3s;
}
.newsletter-form button:hover {
opacity: 0.9;
}
/* Modal */
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
background: white;
border-radius: 10px;
max-width: 800px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
position: relative;
padding: 2rem;
}
.close {
position: absolute;
top: 1rem;
right: 1.5rem;
font-size: 1.5rem;
cursor: pointer;
color: #999;
}
.close:hover {
color: #333;
}
/* Responsive */
@media (max-width: 768px) {
.hero-content h1 {
font-size: 2rem;
}
.delivery-info {
flex-direction: column;
gap: 0.5rem;
}
.newsletter-form {
flex-direction: column;
}
.products-grid {
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}
}
@media (max-width: 480px) {
.products-grid {
grid-template-columns: 1fr;
}
.deals-grid {
grid-template-columns: 1fr;
}
}
</style>
<?php include '../includes/footer.php'; ?>

3. grocery/cart.php (Shopping Cart)

```php
<?php
require_once '../includes/config.php';
require_once 'includes/grocery-config.php';
require_once 'includes/grocery-functions.php';
require_once 'includes/cart.php';

$page_title = 'Shopping Cart';

$pdo = getDB();
$cartItems = [];
$cartTotal = 0;

// Get cart items
if (isLoggedIn()) {
$stmt = $pdo->prepare("
SELECT c.*, p.name, p.slug, p.stock_quantity, p.images,
pv.name as variant_name, pv.sku as variant_sku
FROM cart c
JOIN products p ON c.product_id = p.id
LEFT JOIN product_variants pv ON c.variant_id = pv.id
WHERE c.user_id = ?
");
$stmt->execute([$_SESSION['user_id']]);
$cartItems = $stmt->fetchAll();

$cartTotal = calculateCartTotal();

} else {
$sessionCart = $_SESSION['cart'] ?? [];

foreach ($sessionCart as $item) {
$stmt = $pdo->prepare("SELECT * FROM products WHERE id = ?");
$stmt->execute([$item['product_id']]);
$product = $stmt->fetch();
if ($product) {
$cartItems[] = [
'product_id' => $product['id'],
'quantity' => $item['quantity'],
'price' => $product['price'],
'name' => $product['name'],
'slug' => $product['slug'],
'stock_quantity' => $product['stock_quantity'],
'images' => $product['images']
];
}
}
$cartTotal = calculateCartTotal();

}

// Handle cart updates via AJAX
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
$response = ['success' => false, 'message' => ''];

switch ($_POST['action']) {
case 'update':
$productId = $_POST['product_id'];
$quantity = (int)$_POST['quantity'];
if (updateCartQuantity($productId, $_POST['variant_id'] ?? null, $quantity)) {
$response['success'] = true;
$response['total'] = formatPrice(calculateCartTotal());
$response['count'] = getCartCount();
}
break;
case 'remove':
$productId = $_POST['product_id'];
if (removeFromCart($productId, $_POST['variant_id'] ?? null)) {
$response['success'] = true;
$response['total'] = formatPrice(calculateCartTotal());
$response['count'] = getCartCount();
}
break;
case 'clear':
clearCart();
$response['success'] = true;
$response['total'] = formatPrice(0);
$response['count'] = 0;
break;
}
header('Content-Type: application/json');
echo json_encode($response);
exit;

            item.querySelector('.item-total').textContent = formatPrice(price * newQuantity);
// Update cart totals
document.querySelector('.subtotal').textContent = data.total;
document.querySelector('.grand-total').textContent = data.total;
// Update cart count in header
updateCartCount(data.count);
}
});
}
function removeItem(productId) {
if (!confirm('Remove this item from cart?')) return;
fetch('cart.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `action=remove&product_id=${productId}`
})
.then(response => response.json())
.then(data => {
if (data.success) {
const item = document.querySelector(`.cart-item[data-product-id="${productId}"]`);
item.remove();
// Update cart totals
document.querySelector('.subtotal').textContent = data.total;
document.querySelector('.grand-total').textContent = data.total;
// Update cart count in header
updateCartCount(data.count);
// Check if cart is empty
if (document.querySelectorAll('.cart-item').length === 0) {
location.reload(); // Reload to show empty cart message
}
}
});
}
function clearCart() {
if (!confirm('Clear your entire cart?')) return;
fetch('cart.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'action=clear'
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
}
});
}
function applyCoupon() {
const coupon = document.getElementById('couponCode').value;
const messageEl = document.getElementById('couponMessage');
if (!coupon) {
messageEl.textContent = 'Please enter a coupon code';
messageEl.className = 'coupon-message error';
return;
}
fetch('api/apply-coupon.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ coupon: coupon })
})
.then(response => response.json())
.then(data => {
if (data.success) {
messageEl.textContent = 'Coupon applied successfully!';
messageEl.className = 'coupon-message success';
document.querySelector('.grand-total').textContent = data.new_total;
} else {
messageEl.textContent = data.message || 'Invalid coupon code';
messageEl.className = 'coupon-message error';
}
});
}
function formatPrice(amount) {
return '<?php echo CURRENCY_SYMBOL; ?>' + parseFloat(amount).toFixed(2);
}
function updateCartCount(count) {
const cartBadge = document.querySelector('.cart-count');
if (cartBadge) {
cartBadge.textContent = count;
}
}
</script>
<style>
.cart-container {
max-width: 1200px;
margin: 2rem auto;
padding: 0 1rem;
}
.cart-container h1 {
font-size: 2rem;
color: #333;
margin-bottom: 2rem;
}
/* Empty Cart */
.empty-cart {
text-align: center;
padding: 4rem;
background: white;
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
}
.empty-cart-icon {
font-size: 5rem;
margin-bottom: 1rem;
}
.empty-cart h2 {
color: #333;
margin-bottom: 1rem;
}
.empty-cart p {
color: #666;
margin-bottom: 2rem;
}
.btn-shop {
display: inline-block;
padding: 1rem 2rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
text-decoration: none;
border-radius: 5px;
transition: opacity 0.3s;
}
.btn-shop:hover {
opacity: 0.9;
}
/* Cart Grid */
.cart-grid {
display: grid;
grid-template-columns: 1fr 350px;
gap: 2rem;
}
/* Cart Items */
.cart-items {
background: white;
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
overflow: hidden;
}
.cart-header {
display: grid;
grid-template-columns: 3fr 1fr 1fr 1fr 50px;
padding: 1rem;
background: #f8f9fa;
font-weight: 500;
color: #555;
border-bottom: 1px solid #e9ecef;
}
.cart-item {
display: grid;
grid-template-columns: 3fr 1fr 1fr 1fr 50px;
padding: 1rem;
align-items: center;
border-bottom: 1px solid #e9ecef;
}
.cart-item:last-child {
border-bottom: none;
}
.product-col {
display: flex;
align-items: center;
gap: 1rem;
}
.cart-item-image {
width: 80px;
height: 80px;
object-fit: cover;
border-radius: 5px;
}
.cart-item-info {
flex: 1;
}
.cart-item-name {
color: #333;
text-decoration: none;
font-weight: 500;
margin-bottom: 0.3rem;
display: block;
}
.cart-item-name:hover {
color: #667eea;
}
.cart-item-variant {
color: #888;
font-size: 0.9rem;
}
.price-col, .total-col {
font-weight: 500;
color: #333;
}
.quantity-col {
text-align: center;
}
.cart-quantity {
display: inline-flex;
align-items: center;
border: 1px solid #ddd;
border-radius: 5px;
overflow: hidden;
}
.qty-decrease, .qty-increase {
width: 30px;
height: 35px;
background: #f8f9fa;
border: none;
cursor: pointer;
font-size: 1.2rem;
}
.qty-decrease:hover, .qty-increase:hover {
background: #e9ecef;
}
.qty-input {
width: 50px;
height: 35px;
border: none;
border-left: 1px solid #ddd;
border-right: 1px solid #ddd;
text-align: center;
}
.stock-warning {
margin-top: 0.3rem;
color: #ff6b6b;
font-size: 0.8rem;
}
.remove-item {
background: none;
border: none;
color: #999;
font-size: 1.2rem;
cursor: pointer;
transition: color 0.3s;
}
.remove-item:hover {
color: #dc3545;
}
.cart-actions {
padding: 1rem;
display: flex;
gap: 1rem;
border-top: 1px solid #e9ecef;
}
.btn-clear, .btn-continue {
padding: 0.5rem 1rem;
border: 1px solid #ddd;
border-radius: 5px;
background: white;
color: #666;
text-decoration: none;
cursor: pointer;
transition: all 0.3s;
}
.btn-clear:hover {
background: #dc3545;
color: white;
border-color: #dc3545;
}
.btn-continue:hover {
background: #667eea;
color: white;
border-color: #667eea;
}
/* Cart Summary */
.cart-summary {
background: white;
border-radius: 10px;
padding: 1.5rem;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
height: fit-content;
position: sticky;
top: 20px;
}
.cart-summary h3 {
color: #333;
margin-bottom: 1.5rem;
padding-bottom: 1rem;
border-bottom: 1px solid #e9ecef;
}
.summary-row {
display: flex;
justify-content: space-between;
margin-bottom: 1rem;
color: #666;
}
.summary-row.total {
margin-top: 1rem;
padding-top: 1rem;
border-top: 2px solid #e9ecef;
font-weight: bold;
color: #333;
font-size: 1.2rem;
}
/* Free Delivery Progress */
.free-delivery-progress {
margin: 1rem 0;
padding: 1rem;
background: #f8f9fa;
border-radius: 5px;
}
.progress-text {
color: #666;
font-size: 0.9rem;
margin-bottom: 0.5rem;
}
.progress-bar {
height: 8px;
background: #e9ecef;
border-radius: 4px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
transition: width 0.3s;
}
/* Coupon Section */
.coupon-section {
margin: 1.5rem 0;
}
.coupon-section h4 {
color: #333;
margin-bottom: 0.5rem;
}
.coupon-form {
display: flex;
gap: 0.5rem;
}
.coupon-form input {
flex: 1;
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 3px;
}
.btn-apply {
padding: 0.5rem 1rem;
background: #667eea;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
}
.btn-apply:hover {
opacity: 0.9;
}
.coupon-message {
margin-top: 0.5rem;
font-size: 0.9rem;
}
.coupon-message.success {
color: #28a745;
}
.coupon-message.error {
color: #dc3545;
}
/* Checkout Button */
.btn-checkout {
display: block;
width: 100%;
padding: 1rem;
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
color: white;
text-align: center;
text-decoration: none;
border-radius: 5px;
font-weight: 500;
margin: 1.5rem 0;
transition: opacity 0.3s;
}
.btn-checkout:hover {
opacity: 0.9;
}
/* Payment Methods */
.payment-methods p {
color: #666;
margin-bottom: 0.5rem;
font-size: 0.9rem;
}
.payment-icons {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.payment-icon {
padding: 0.3rem 0.8rem;
background: #f8f9fa;
border: 1px solid #ddd;
border-radius: 3px;
font-size: 0.85rem;
color: #666;
}
/* Responsive */
@media (max-width: 1024px) {
.cart-grid {
grid-template-columns: 1fr;
}
.cart-summary {
position: static;
}
}
@media (max-width: 768px) {
.cart-header {
display: none;
}
.cart-item {
grid-template-columns: 1fr;
gap: 1rem;
}
.product-col {
grid-column: 1 / -1;
}
.price-col::before {
content: 'Price: ';
font-weight: normal;
color: #666;
}
.quantity-col::before {
content: 'Quantity: ';
font-weight: normal;
color: #666;
}
.total-col::before {
content: 'Total: ';
font-weight: normal;
color: #666;
}
.price-col, .quantity-col, .total-col {
display: flex;
justify-content: space-between;
align-items: center;
}
.action-col {
position: absolute;
top: 1rem;
right: 1rem;
}
.cart-item {
position: relative;
}
}
</style>
<?php include '../includes/footer.php'; ?>

4. grocery/checkout.php (Checkout Process)

<?php
require_once '../includes/config.php';
require_once 'includes/grocery-config.php';
require_once 'includes/grocery-functions.php';
require_once 'includes/cart.php';
require_once 'includes/checkout.php';
requireLogin();
$page_title = 'Checkout';
$pdo = getDB();
$user = getCurrentUser();
// Get user addresses
$stmt = $pdo->prepare("SELECT * FROM addresses WHERE user_id = ? ORDER BY is_default DESC");
$stmt->execute([$user['id']]);
$addresses = $stmt->fetchAll();
// Get cart items
$cartItems = [];
$cartTotal = 0;
$stmt = $pdo->prepare("
SELECT c.*, p.name, p.slug, p.stock_quantity, p.images
FROM cart c
JOIN products p ON c.product_id = p.id
WHERE c.user_id = ?
");
$stmt->execute([$user['id']]);
$cartItems = $stmt->fetchAll();
$cartTotal = calculateCartTotal();
// Redirect if cart is empty
if (empty($cartItems)) {
header('Location: cart.php');
exit;
}
// Get delivery slots
$deliverySlots = $pdo->query("
SELECT * FROM delivery_slots 
WHERE is_active = 1 
ORDER BY day_of_week, start_time
")->fetchAll();
// Handle form submission
$errors = [];
$orderPlaced = false;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Validate input
$addressId = $_POST['address_id'] ?? '';
$paymentMethod = $_POST['payment_method'] ?? '';
$deliveryType = $_POST['delivery_type'] ?? 'delivery';
$deliverySlot = $_POST['delivery_slot'] ?? '';
$specialInstructions = $_POST['special_instructions'] ?? '';
$allowSubstitutions = isset($_POST['allow_substitutions']);
if (empty($addressId)) {
$errors['address'] = 'Please select a delivery address';
}
if (empty($paymentMethod)) {
$errors['payment'] = 'Please select a payment method';
}
if ($deliveryType === 'delivery' && empty($deliverySlot)) {
$errors['delivery_slot'] = 'Please select a delivery slot';
}
// Validate stock availability
foreach ($cartItems as $item) {
if ($item['quantity'] > $item['stock_quantity']) {
$errors['stock'] = "Sorry, {$item['name']} is no longer available in the requested quantity";
break;
}
}
if (empty($errors)) {
// Calculate totals
$subtotal = $cartTotal;
$shipping = ($deliveryType === 'delivery' && $cartTotal < FREE_DELIVERY_MIN) ? DELIVERY_CHARGE : 0;
$tax = $subtotal * (DEFAULT_TAX_RATE / 100);
$total = $subtotal + $shipping + $tax;
// Begin transaction
$pdo->beginTransaction();
try {
// Create order
$orderNumber = generateOrderNumber();
$stmt = $pdo->prepare("
INSERT INTO orders (
order_number, user_id, address_id, subtotal, shipping_amount,
tax_amount, total_amount, payment_method, delivery_type,
delivery_slot, special_instructions, allow_substitutions,
order_status, payment_status
) VALUES (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', 'pending'
)
");
$stmt->execute([
$orderNumber,
$user['id'],
$addressId,
$subtotal,
$shipping,
$tax,
$total,
$paymentMethod,
$deliveryType,
$deliverySlot,
$specialInstructions,
$allowSubstitutions ? 1 : 0
]);
$orderId = $pdo->lastInsertId();
// Add order items
foreach ($cartItems as $item) {
$itemTotal = $item['price'] * $item['quantity'];
$itemTax = $itemTotal * (DEFAULT_TAX_RATE / 100);
$stmt = $pdo->prepare("
INSERT INTO order_items (
order_id, product_id, product_name, quantity,
price, subtotal, tax_amount, total_amount
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
");
$stmt->execute([
$orderId,
$item['product_id'],
$item['name'],
$item['quantity'],
$item['price'],
$itemTotal,
$itemTax,
$itemTotal + $itemTax
]);
// Update inventory
$pdo->prepare("
UPDATE products 
SET stock_quantity = stock_quantity - ?,
sold_count = sold_count + ?
WHERE id = ?
")->execute([$item['quantity'], $item['quantity'], $item['product_id']]);
}
// Clear cart
$pdo->prepare("DELETE FROM cart WHERE user_id = ?")->execute([$user['id']]);
// Award loyalty points
$pointsEarned = floor($total * LOYALTY_POINTS_RATE);
$pdo->prepare("
UPDATE users 
SET loyalty_points = loyalty_points + ? 
WHERE id = ?
")->execute([$pointsEarned, $user['id']]);
$pdo->commit();
// Redirect to confirmation
$_SESSION['order_success'] = $orderNumber;
header('Location: order-confirmation.php?order=' . $orderNumber);
exit;
} catch (Exception $e) {
$pdo->rollBack();
$errors['general'] = 'Failed to place order. Please try again.';
error_log("Order failed: " . $e->getMessage());
}
}
}
include '../includes/header.php';
?>
<link rel="stylesheet" href="css/grocery.css">
<link rel="stylesheet" href="css/checkout.css">
<div class="checkout-container">
<h1>Checkout</h1>
<?php if (!empty($errors['general'])): ?>
<div class="alert error"><?php echo $errors['general']; ?></div>
<?php endif; ?>
<div class="checkout-grid">
<!-- Checkout Form -->
<div class="checkout-form">
<form method="POST" id="checkoutForm">
<!-- Delivery Address -->
<div class="checkout-section">
<h2>Delivery Address</h2>
<?php if (!empty($errors['address'])): ?>
<div class="field-error"><?php echo $errors['address']; ?></div>
<?php endif; ?>
<?php if (empty($addresses)): ?>
<div class="no-address">
<p>You haven't added any addresses yet.</p>
<a href="account/addresses.php?add=1" class="btn-add-address">Add New Address</a>
</div>
<?php else: ?>
<div class="address-list">
<?php foreach ($addresses as $address): ?>
<label class="address-radio">
<input type="radio" 
name="address_id" 
value="<?php echo $address['id']; ?>"
<?php echo $address['is_default'] ? 'checked' : ''; ?>>
<div class="address-card">
<div class="address-type"><?php echo ucfirst($address['address_type']); ?></div>
<div class="address-details">
<p><strong><?php echo htmlspecialchars($address['recipient_name']); ?></strong></p>
<p><?php echo htmlspecialchars($address['address_line1']); ?></p>
<?php if ($address['address_line2']): ?>
<p><?php echo htmlspecialchars($address['address_line2']); ?></p>
<?php endif; ?>
<p><?php echo htmlspecialchars($address['city'] . ', ' . $address['state'] . ' ' . $address['postal_code']); ?></p>
<p>Phone: <?php echo htmlspecialchars($address['recipient_phone']); ?></p>
</div>
<?php if ($address['delivery_instructions']): ?>
<div class="delivery-instructions">
<strong>Instructions:</strong> <?php echo htmlspecialchars($address['delivery_instructions']); ?>
</div>
<?php endif; ?>
</div>
</label>
<?php endforeach; ?>
</div>
<div class="address-actions">
<a href="account/addresses.php" class="btn-manage-addresses">Manage Addresses</a>
</div>
<?php endif; ?>
</div>
<!-- Delivery Options -->
<div class="checkout-section">
<h2>Delivery Options</h2>
<div class="delivery-options">
<label class="delivery-radio">
<input type="radio" name="delivery_type" value="delivery" checked>
<div class="delivery-card">
<span class="delivery-title">Home Delivery</span>
<span class="delivery-price">
<?php echo $cartTotal >= FREE_DELIVERY_MIN ? 'Free' : formatPrice(DELIVERY_CHARGE); ?>
</span>
</div>
</label>
<label class="delivery-radio">
<input type="radio" name="delivery_type" value="pickup">
<div class="delivery-card">
<span class="delivery-title">Store Pickup</span>
<span class="delivery-price">Free</span>
</div>
</label>
</div>
<!-- Delivery Slots -->
<div id="deliverySlots" class="delivery-slots">
<h3>Select Delivery Time</h3>
<?php if (!empty($errors['delivery_slot'])): ?>
<div class="field-error"><?php echo $errors['delivery_slot']; ?></div>
<?php endif; ?>
<div class="slots-grid">
<?php 
$days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
foreach ($deliverySlots as $slot): 
?>
<label class="slot-radio">
<input type="radio" name="delivery_slot" value="<?php echo $slot['id']; ?>">
<div class="slot-card">
<span class="slot-day"><?php echo $days[$slot['day_of_week']]; ?></span>
<span class="slot-time">
<?php echo date('h:i A', strtotime($slot['start_time'])); ?> - 
<?php echo date('h:i A', strtotime($slot['end_time'])); ?>
</span>
</div>
</label>
<?php endforeach; ?>
</div>
</div>
<!-- Special Instructions -->
<div class="form-group">
<label for="special_instructions">Special Instructions (Optional)</label>
<textarea id="special_instructions" 
name="special_instructions" 
rows="3"
placeholder="Add any special instructions for delivery..."><?php echo htmlspecialchars($_POST['special_instructions'] ?? ''); ?></textarea>
</div>
<!-- Substitutions -->
<div class="form-checkbox">
<label>
<input type="checkbox" name="allow_substitutions" checked>
Allow substitutions if an item is out of stock
</label>
<small class="help-text">We'll replace with similar items of equal or greater value</small>
</div>
</div>
<!-- Payment Method -->
<div class="checkout-section">
<h2>Payment Method</h2>
<?php if (!empty($errors['payment'])): ?>
<div class="field-error"><?php echo $errors['payment']; ?></div>
<?php endif; ?>
<div class="payment-options">
<label class="payment-radio">
<input type="radio" name="payment_method" value="credit_card" checked>
<div class="payment-card">
<span class="payment-icon">💳</span>
<span class="payment-title">Credit / Debit Card</span>
</div>
</label>
<label class="payment-radio">
<input type="radio" name="payment_method" value="paypal">
<div class="payment-card">
<span class="payment-icon">🅿️</span>
<span class="payment-title">PayPal</span>
</div>
</label>
<label class="payment-radio">
<input type="radio" name="payment_method" value="cash_on_delivery">
<div class="payment-card">
<span class="payment-icon">💵</span>
<span class="payment-title">Cash on Delivery</span>
</div>
</label>
</div>
<!-- Credit Card Fields (shown when credit card selected) -->
<div id="creditCardFields" class="credit-card-fields">
<div class="form-row">
<div class="form-group">
<label for="card_number">Card Number</label>
<input type="text" id="card_number" placeholder="1234 5678 9012 3456">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="expiry_date">Expiry Date</label>
<input type="text" id="expiry_date" placeholder="MM/YY">
</div>
<div class="form-group">
<label for="cvv">CVV</label>
<input type="text" id="cvv" placeholder="123">
</div>
</div>
<div class="form-group">
<label for="card_name">Name on Card</label>
<input type="text" id="card_name" placeholder="John Doe">
</div>
</div>
</div>
<!-- Order Summary (Mobile) -->
<div class="checkout-summary-mobile">
<h2>Order Summary</h2>
<div class="summary-items">
<?php foreach ($cartItems as $item): ?>
<div class="summary-item">
<span class="item-name"><?php echo htmlspecialchars($item['name']); ?> x<?php echo $item['quantity']; ?></span>
<span class="item-price"><?php echo formatPrice($item['price'] * $item['quantity']); ?></span>
</div>
<?php endforeach; ?>
</div>
<div class="summary-totals">
<div class="summary-row">
<span>Subtotal</span>
<span><?php echo formatPrice($cartTotal); ?></span>
</div>
<div class="summary-row">
<span>Shipping</span>
<span class="shipping-display">
<?php echo $cartTotal >= FREE_DELIVERY_MIN ? 'Free' : formatPrice(DELIVERY_CHARGE); ?>
</span>
</div>
<div class="summary-row">
<span>Tax (<?php echo DEFAULT_TAX_RATE; ?>%)</span>
<span class="tax-display">
<?php echo formatPrice($cartTotal * (DEFAULT_TAX_RATE / 100)); ?>
</span>
</div>
<div class="summary-row total">
<span>Total</span>
<span class="total-display">
<?php 
$shipping = $cartTotal >= FREE_DELIVERY_MIN ? 0 : DELIVERY_CHARGE;
$tax = $cartTotal * (DEFAULT_TAX_RATE / 100);
echo formatPrice($cartTotal + $shipping + $tax);
?>
</span>
</div>
</div>
</div>
<!-- Place Order Button -->
<button type="submit" class="btn-place-order" id="placeOrderBtn">
<span class="btn-text">Place Order</span>
<span class="btn-loader" style="display: none;">⏳</span>
</button>
</form>
</div>
<!-- Order Summary (Desktop) -->
<div class="checkout-summary">
<h2>Order Summary</h2>
<div class="summary-items">
<?php foreach ($cartItems as $item): 
$images = json_decode($item['images'], true);
$image = $images[0] ?? '/blog-website/assets/product-placeholder.jpg';
?>
<div class="summary-item">
<img src="<?php echo htmlspecialchars($image); ?>" 
alt="<?php echo htmlspecialchars($item['name']); ?>"
class="summary-item-image">
<div class="summary-item-details">
<div class="item-name"><?php echo htmlspecialchars($item['name']); ?></div>
<div class="item-quantity">Qty: <?php echo $item['quantity']; ?></div>
</div>
<div class="item-price"><?php echo formatPrice($item['price'] * $item['quantity']); ?></div>
</div>
<?php endforeach; ?>
</div>
<div class="summary-totals">
<div class="summary-row">
<span>Subtotal</span>
<span><?php echo formatPrice($cartTotal); ?></span>
</div>
<div class="summary-row">
<span>Shipping</span>
<span class="shipping-display">
<?php echo $cartTotal >= FREE_DELIVERY_MIN ? 'Free' : formatPrice(DELIVERY_CHARGE); ?>
</span>
</div>
<div class="summary-row">
<span>Tax (<?php echo DEFAULT_TAX_RATE; ?>%)</span>
<span class="tax-display">
<?php echo formatPrice($cartTotal * (DEFAULT_TAX_RATE / 100)); ?>
</span>
</div>
<div class="summary-row total">
<span>Total</span>
<span class="total-display">
<?php 
$shipping = $cartTotal >= FREE_DELIVERY_MIN ? 0 : DELIVERY_CHARGE;
$tax = $cartTotal * (DEFAULT_TAX_RATE / 100);
echo formatPrice($cartTotal + $shipping + $tax);
?>
</span>
</div>
</div>
<!-- Loyalty Points -->
<div class="loyalty-info">
<div class="loyalty-icon">⭐</div>
<div class="loyalty-text">
You'll earn <strong><?php echo floor(($cartTotal + $shipping + $tax) * LOYALTY_POINTS_RATE); ?> points</strong>
on this purchase
</div>
</div>
<!-- Secure Checkout -->
<div class="secure-checkout">
<div class="secure-icon">🔒</div>
<div class="secure-text">
<strong>Secure Checkout</strong>
<span>Your information is encrypted and secure</span>
</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Toggle delivery slots based on delivery type
const deliveryRadios = document.querySelectorAll('input[name="delivery_type"]');
const deliverySlots = document.getElementById('deliverySlots');
deliveryRadios.forEach(radio => {
radio.addEventListener('change', function() {
if (this.value === 'delivery') {
deliverySlots.style.display = 'block';
} else {
deliverySlots.style.display = 'none';
}
});
});
// Toggle credit card fields
const paymentRadios = document.querySelectorAll('input[name="payment_method"]');
const creditCardFields = document.getElementById('creditCardFields');
paymentRadios.forEach(radio => {
radio.addEventListener('change', function() {
if (this.value === 'credit_card') {
creditCardFields.style.display = 'block';
} else {
creditCardFields.style.display = 'none';
}
});
});
// Form submission
const form = document.getElementById('checkoutForm');
const placeOrderBtn = document.getElementById('placeOrderBtn');
form.addEventListener('submit', function() {
const btnText = placeOrderBtn.querySelector('.btn-text');
const btnLoader = placeOrderBtn.querySelector('.btn-loader');
btnText.style.display = 'none';
btnLoader.style.display = 'inline-block';
placeOrderBtn.disabled = true;
});
// Credit card formatting
const cardNumber = document.getElementById('card_number');
if (cardNumber) {
cardNumber.addEventListener('input', function(e) {
let value = e.target.value.replace(/\D/g, '');
value = value.replace(/(\d{4})(?=\d)/g, '$1 ');
e.target.value = value;
});
}
const expiryDate = document.getElementById('expiry_date');
if (expiryDate) {
expiryDate.addEventListener('input', function(e) {
let value = e.target.value.replace(/\D/g, '');
if (value.length >= 2) {
value = value.substring(0, 2) + '/' + value.substring(2, 4);
}
e.target.value = value;
});
}
});
</script>
<style>
.checkout-container {
max-width: 1400px;
margin: 2rem auto;
padding: 0 1rem;
}
.checkout-container h1 {
font-size: 2rem;
color: #333;
margin-bottom: 2rem;
}
.checkout-grid {
display: grid;
grid-template-columns: 1fr 400px;
gap: 2rem;
}
/* Checkout Form */
.checkout-form {
background: white;
border-radius: 10px;
padding: 2rem;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
}
.checkout-section {
margin-bottom: 2rem;
padding-bottom: 2rem;
border-bottom: 1px solid #eee;
}
.checkout-section:last-child {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
.checkout-section h2 {
color: #333;
font-size: 1.2rem;
margin-bottom: 1.5rem;
}
/* Address Selection */
.address-list {
display: flex;
flex-direction: column;
gap: 1rem;
margin-bottom: 1rem;
}
.address-radio {
display: flex;
gap: 1rem;
cursor: pointer;
}
.address-radio input[type="radio"] {
margin-top: 1.5rem;
}
.address-card {
flex: 1;
padding: 1rem;
border: 1px solid #ddd;
border-radius: 5px;
transition: border-color 0.3s;
}
.address-radio input[type="radio"]:checked + .address-card {
border-color: #667eea;
background: #f8f9fa;
}
.address-type {
display: inline-block;
padding: 0.2rem 0.5rem;
background: #667eea;
color: white;
border-radius: 3px;
font-size: 0.8rem;
margin-bottom: 0.5rem;
}
.address-details p {
margin: 0.2rem 0;
color: #666;
}
.delivery-instructions {
margin-top: 0.5rem;
padding: 0.5rem;
background: #f8f9fa;
border-radius: 3px;
font-size: 0.9rem;
color: #666;
}
.address-actions {
text-align: right;
}
.btn-manage-addresses {
color: #667eea;
text-decoration: none;
}
.no-address {
text-align: center;
padding: 2rem;
background: #f8f9fa;
border-radius: 5px;
}
.btn-add-address {
display: inline-block;
margin-top: 1rem;
padding: 0.5rem 1rem;
background: #667eea;
color: white;
text-decoration: none;
border-radius: 3px;
}
/* Delivery Options */
.delivery-options {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
margin-bottom: 1.5rem;
}
.delivery-radio {
cursor: pointer;
}
.delivery-radio input[type="radio"] {
display: none;
}
.delivery-card {
padding: 1rem;
border: 1px solid #ddd;
border-radius: 5px;
text-align: center;
transition: all 0.3s;
}
.delivery-radio input[type="radio"]:checked + .delivery-card {
border-color: #667eea;
background: #f8f9fa;
}
.delivery-title {
display: block;
font-weight: 500;
margin-bottom: 0.3rem;
}
.delivery-price {
color: #28a745;
}
/* Delivery Slots */
.delivery-slots h3 {
font-size: 1rem;
color: #333;
margin-bottom: 1rem;
}
.slots-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 0.5rem;
margin-bottom: 1.5rem;
}
.slot-radio {
cursor: pointer;
}
.slot-radio input[type="radio"] {
display: none;
}
.slot-card {
padding: 0.8rem;
border: 1px solid #ddd;
border-radius: 5px;
text-align: center;
}
.slot-radio input[type="radio"]:checked + .slot-card {
border-color: #667eea;
background: #f8f9fa;
}
.slot-day {
display: block;
font-weight: 500;
margin-bottom: 0.2rem;
}
.slot-time {
font-size: 0.9rem;
color: #666;
}
/* Payment Options */
.payment-options {
display: flex;
flex-direction: column;
gap: 0.5rem;
margin-bottom: 1.5rem;
}
.payment-radio {
cursor: pointer;
}
.payment-radio input[type="radio"] {
display: none;
}
.payment-card {
display: flex;
align-items: center;
gap: 1rem;
padding: 1rem;
border: 1px solid #ddd;
border-radius: 5px;
}
.payment-radio input[type="radio"]:checked + .payment-card {
border-color: #667eea;
background: #f8f9fa;
}
.payment-icon {
font-size: 1.5rem;
}
.payment-title {
font-weight: 500;
}
/* Credit Card Fields */
.credit-card-fields {
margin-top: 1rem;
padding: 1rem;
background: #f8f9fa;
border-radius: 5px;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
margin-bottom: 1rem;
}
.form-group {
margin-bottom: 1rem;
}
.form-group label {
display: block;
margin-bottom: 0.3rem;
color: #666;
font-size: 0.9rem;
}
.form-group input,
.form-group textarea {
width: 100%;
padding: 0.8rem;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 1rem;
}
.form-group textarea {
resize: vertical;
}
.form-checkbox {
margin: 1rem 0;
}
.form-checkbox label {
display: flex;
align-items: center;
gap: 0.5rem;
cursor: pointer;
}
.help-text {
display: block;
color: #888;
font-size: 0.85rem;
margin-top: 0.3rem;
}
.field-error {
color: #dc3545;
font-size: 0.9rem;
margin-bottom: 0.5rem;
}
/* Order Summary */
.checkout-summary {
background: white;
border-radius: 10px;
padding: 1.5rem;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
height: fit-content;
position: sticky;
top: 20px;
}
.checkout-summary h2,
.checkout-summary-mobile h2 {
color: #333;
font-size: 1.2rem;
margin-bottom: 1.5rem;
padding-bottom: 1rem;
border-bottom: 1px solid #eee;
}
.summary-items {
max-height: 300px;
overflow-y: auto;
margin-bottom: 1rem;
}
.summary-item {
display: flex;
align-items: center;
gap: 1rem;
padding: 0.5rem 0;
border-bottom: 1px solid #f0f0f0;
}
.summary-item:last-child {
border-bottom: none;
}
.summary-item-image {
width: 50px;
height: 50px;
object-fit: cover;
border-radius: 5px;
}
.summary-item-details {
flex: 1;
}
.item-name {
font-weight: 500;
color: #333;
margin-bottom: 0.2rem;
}
.item-quantity {
font-size: 0.9rem;
color: #888;
}
.item-price {
font-weight: 500;
color: #333;
}
.summary-totals {
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid #eee;
}
.summary-row {
display: flex;
justify-content: space-between;
margin-bottom: 0.5rem;
color: #666;
}
.summary-row.total {
margin-top: 0.5rem;
padding-top: 0.5rem;
border-top: 1px solid #eee;
font-weight: bold;
color: #333;
font-size: 1.2rem;
}
/* Loyalty Info */
.loyalty-info {
display: flex;
align-items: center;
gap: 0.5rem;
margin: 1.5rem 0;
padding: 1rem;
background: #f8f9fa;
border-radius: 5px;
}
.loyalty-icon {
font-size: 1.5rem;
}
.loyalty-text {
color: #666;
line-height: 1.4;
}
/* Secure Checkout */
.secure-checkout {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 1rem;
background: #f8f9fa;
border-radius: 5px;
}
.secure-icon {
font-size: 1.5rem;
}
.secure-text {
flex: 1;
}
.secure-text strong {
display: block;
color: #333;
margin-bottom: 0.2rem;
}
.secure-text span {
color: #666;
font-size: 0.9rem;
}
/* Place Order Button */
.btn-place-order {
width: 100%;
padding: 1rem;
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
color: white;
border: none;
border-radius: 5px;
font-size: 1.2rem;
font-weight: 500;
cursor: pointer;
transition: opacity 0.3s;
margin-top: 1rem;
}
.btn-place-order:hover:not(:disabled) {
opacity: 0.9;
}
.btn-place-order:disabled {
opacity: 0.6;
cursor: not-allowed;
}
/* Mobile Summary */
.checkout-summary-mobile {
display: none;
background: white;
border-radius: 10px;
padding: 1.5rem;
margin-top: 1rem;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
}
/* Alert */
.alert {
padding: 1rem;
border-radius: 5px;
margin-bottom: 1rem;
}
.alert.error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
/* Responsive */
@media (max-width: 1024px) {
.checkout-grid {
grid-template-columns: 1fr;
}
.checkout-summary {
display: none;
}
.checkout-summary-mobile {
display: block;
}
}
@media (max-width: 768px) {
.delivery-options {
grid-template-columns: 1fr;
}
.form-row {
grid-template-columns: 1fr;
}
.slots-grid {
grid-template-columns: 1fr;
}
}
</style>
<?php include '../includes/footer.php'; ?>

5. grocery/order-confirmation.php (Order Success Page)

<?php
require_once '../includes/config.php';
require_once 'includes/grocery-config.php';
require_once 'includes/grocery-functions.php';
$orderNumber = $_GET['order'] ?? $_SESSION['order_success'] ?? '';
if (empty($orderNumber)) {
header('Location: index.php');
exit;
}
$pdo = getDB();
// Get order details
$stmt = $pdo->prepare("
SELECT o.*, a.*, u.email, u.first_name, u.last_name
FROM orders o
JOIN addresses a ON o.address_id = a.id
JOIN users u ON o.user_id = u.id
WHERE o.order_number = ?
");
$stmt->execute([$orderNumber]);
$order = $stmt->fetch();
if (!$order) {
header('Location: index.php');
exit;
}
// Get order items
$stmt = $pdo->prepare("SELECT * FROM order_items WHERE order_id = ?");
$stmt->execute([$order['id']]);
$items = $stmt->fetchAll();
// Clear session success
unset($_SESSION['order_success']);
$page_title = 'Order Confirmation';
include '../includes/header.php';
?>
<link rel="stylesheet" href="css/grocery.css">
<div class="confirmation-container">
<div class="confirmation-card">
<div class="success-icon">✓</div>
<h1>Thank You for Your Order!</h1>
<p class="order-number">Order #<?php echo htmlspecialchars($order['order_number']); ?></p>
<div class="confirmation-message">
<p>We've received your order and will start processing it right away.</p>
<p>A confirmation email has been sent to <strong><?php echo htmlspecialchars($order['email']); ?></strong></p>
</div>
<div class="order-details">
<h2>Order Details</h2>
<div class="details-grid">
<div class="detail-item">
<span class="detail-label">Order Date:</span>
<span class="detail-value"><?php echo date('F j, Y g:i A', strtotime($order['created_at'])); ?></span>
</div>
<div class="detail-item">
<span class="detail-label">Payment Method:</span>
<span class="detail-value">
<?php 
$methods = [
'credit_card' => 'Credit Card',
'paypal' => 'PayPal',
'cash_on_delivery' => 'Cash on Delivery'
];
echo $methods[$order['payment_method']] ?? $order['payment_method'];
?>
</span>
</div>
<div class="detail-item">
<span class="detail-label">Delivery Type:</span>
<span class="detail-value"><?php echo ucfirst($order['delivery_type']); ?></span>
</div>
<?php if ($order['delivery_type'] === 'delivery' && $order['delivery_slot']): ?>
<div class="detail-item">
<span class="detail-label">Delivery Time:</span>
<span class="detail-value">
<?php 
$slot = $pdo->prepare("SELECT * FROM delivery_slots WHERE id = ?");
$slot->execute([$order['delivery_slot']]);
$slotData = $slot->fetch();
if ($slotData) {
$days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
echo $days[$slotData['day_of_week']] . ' ' . 
date('h:i A', strtotime($slotData['start_time'])) . ' - ' . 
date('h:i A', strtotime($slotData['end_time']));
}
?>
</span>
</div>
<?php endif; ?>
</div>
<div class="address-details">
<h3>Delivery Address</h3>
<p><strong><?php echo htmlspecialchars($order['recipient_name']); ?></strong></p>
<p><?php echo htmlspecialchars($order['address_line1']); ?></p>
<?php if ($order['address_line2']): ?>
<p><?php echo htmlspecialchars($order['address_line2']); ?></p>
<?php endif; ?>
<p><?php echo htmlspecialchars($order['city'] . ', ' . $order['state'] . ' ' . $order['postal_code']); ?></p>
<p>Phone: <?php echo htmlspecialchars($order['recipient_phone']); ?></p>
</div>
</div>
<!-- Order Items -->
<div class="order-items">
<h2>Items Ordered</h2>
<table class="items-table">
<thead>
<tr>
<th>Product</th>
<th>Quantity</th>
<th>Price</th>
<th>Total</th>
</tr>
</thead>
<tbody>
<?php foreach ($items as $item): ?>
<tr>
<td><?php echo htmlspecialchars($item['product_name']); ?></td>
<td><?php echo $item['quantity']; ?></td>
<td><?php echo formatPrice($item['price']); ?></td>
<td><?php echo formatPrice($item['subtotal']); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot>
<tr>
<td colspan="3" class="text-right">Subtotal:</td>
<td><?php echo formatPrice($order['subtotal']); ?></td>
</tr>
<tr>
<td colspan="3" class="text-right">Shipping:</td>
<td><?php echo $order['shipping_amount'] > 0 ? formatPrice($order['shipping_amount']) : 'Free'; ?></td>
</tr>
<tr>
<td colspan="3" class="text-right">Tax:</td>
<td><?php echo formatPrice($order['tax_amount']); ?></td>
</tr>
<tr class="total-row">
<td colspan="3" class="text-right">Total:</td>
<td><?php echo formatPrice($order['total_amount']); ?></td>
</tr>
</tfoot>
</table>
</div>
<!-- Loyalty Points -->
<?php if ($order['loyalty_points_earned'] > 0): ?>
<div class="points-earned">
<div class="points-icon">⭐</div>
<div class="points-text">
You earned <strong><?php echo $order['loyalty_points_earned']; ?> points</strong> on this purchase!
</div>
</div>
<?php endif; ?>
<!-- Action Buttons -->
<div class="action-buttons">
<a href="account/orders.php" class="btn-primary">View My Orders</a>
<a href="shop.php" class="btn-secondary">Continue Shopping</a>
</div>
<!-- Track Order -->
<div class="track-order">
<p>Want to track your order?</p>
<a href="track-order.php?order=<?php echo $order['order_number']; ?>" class="track-link">
Track Order Status →
</a>
</div>
</div>
</div>
<style>
.confirmation-container {
max-width: 800px;
margin: 2rem auto;
padding: 0 1rem;
}
.confirmation-card {
background: white;
border-radius: 10px;
padding: 2rem;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
text-align: center;
}
.success-icon {
width: 80px;
height: 80px;
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
color: white;
font-size: 40px;
line-height: 80px;
border-radius: 50%;
margin: 0 auto 1.5rem;
}
.confirmation-card h1 {
color: #333;
margin-bottom: 0.5rem;
}
.order-number {
font-size: 1.2rem;
color: #667eea;
font-weight: 500;
margin-bottom: 1rem;
}
.confirmation-message {
color: #666;
margin-bottom: 2rem;
padding-bottom: 2rem;
border-bottom: 1px solid #eee;
}
.confirmation-message p {
margin: 0.5rem 0;
}
.order-details {
text-align: left;
margin-bottom: 2rem;
}
.order-details h2 {
color: #333;
font-size: 1.2rem;
margin-bottom: 1rem;
}
.details-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
margin-bottom: 1.5rem;
padding: 1rem;
background: #f8f9fa;
border-radius: 5px;
}
.detail-item {
display: flex;
flex-direction: column;
}
.detail-label {
font-size: 0.85rem;
color: #888;
margin-bottom: 0.3rem;
}
.detail-value {
color: #333;
font-weight: 500;
}
.address-details {
padding: 1rem;
background: #f8f9fa;
border-radius: 5px;
}
.address-details h3 {
color: #333;
font-size: 1rem;
margin-bottom: 0.5rem;
}
.address-details p {
color: #666;
margin: 0.2rem 0;
}
.order-items {
text-align: left;
margin: 2rem 0;
}
.order-items h2 {
color: #333;
font-size: 1.2rem;
margin-bottom: 1rem;
}
.items-table {
width: 100%;
border-collapse: collapse;
}
.items-table th {
text-align: left;
padding: 0.5rem;
background: #f8f9fa;
color: #555;
font-weight: 500;
}
.items-table td {
padding: 0.5rem;
border-bottom: 1px solid #eee;
}
.items-table tfoot td {
padding: 0.5rem;
border-top: 1px solid #ddd;
}
.text-right {
text-align: right;
}
.total-row {
font-weight: bold;
color: #333;
}
.points-earned {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 1rem;
background: #f8f9fa;
border-radius: 5px;
margin: 2rem 0;
}
.points-icon {
font-size: 1.5rem;
}
.points-text {
color: #666;
}
.action-buttons {
display: flex;
gap: 1rem;
justify-content: center;
margin: 2rem 0;
}
.btn-primary, .btn-secondary {
padding: 0.8rem 2rem;
border-radius: 5px;
text-decoration: none;
transition: opacity 0.3s;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-secondary {
background: #6c757d;
color: white;
}
.btn-primary:hover, .btn-secondary:hover {
opacity: 0.9;
}
.track-order {
margin-top: 2rem;
padding-top: 2rem;
border-top: 1px solid #eee;
}
.track-order p {
color: #666;
margin-bottom: 0.5rem;
}
.track-link {
color: #667eea;
text-decoration: none;
font-weight: 500;
}
.track-link:hover {
text-decoration: underline;
}
@media (max-width: 768px) {
.details-grid {
grid-template-columns: 1fr;
}
.action-buttons {
flex-direction: column;
}
.items-table {
font-size: 0.9rem;
}
}
</style>
<?php include '../includes/footer.php'; ?>

🚀 How to Use This Project Step by Step

Step 1: Environment Setup

  1. Ensure PHP 7.4+ and MySQL are installed
  2. Install Composer
  3. Enable required PHP extensions: pdo_mysql, json, gd (for images)

Step 2: Database Setup

  1. Create database: grocery_db
  2. Import database/grocery.sql
  3. Update .env with database credentials

Step 3: Configure Settings

  1. Copy .env.example to .env
  2. Update store information
  3. Configure tax rates, shipping charges, and loyalty points
  4. Set up payment gateway credentials (Stripe/PayPal)

Step 4: Directory Permissions

chmod -R 777 uploads/products/
chmod -R 777 uploads/categories/
chmod -R 777 uploads/receipts/

Step 5: Initial Setup

  1. Navigate to /grocery/index.php
  2. Register a customer account
  3. Login as admin (default: [email protected] / password)

Step 6: Add Products

  1. Go to Admin Dashboard → Products
  2. Add categories first
  3. Add products with images, prices, and stock
  4. Set up featured products and deals

Step 7: Configure Delivery

  1. Go to Admin Dashboard → Settings
  2. Set up delivery slots
  3. Configure delivery zones and charges

Step 8: Test Order Flow

  1. Browse products and add to cart
  2. Proceed to checkout
  3. Place a test order
  4. Verify order in admin panel

🔒 Security Features

Authentication & Authorization

  • ✅ Password hashing with bcrypt
  • ✅ Session management
  • ✅ Role-based access (Customer, Staff, Admin)
  • ✅ Login attempt limiting

Payment Security

  • ✅ PCI compliance ready
  • ✅ SSL/TLS encryption
  • ✅ Payment tokenization
  • ✅ No sensitive data storage

Data Protection

  • ✅ Input sanitization
  • ✅ SQL injection prevention
  • ✅ XSS protection
  • ✅ CSRF tokens on forms

📊 Features Summary

Customer Features

  • ✅ Browse products by category
  • ✅ Advanced search and filters
  • ✅ Product details with images
  • ✅ Shopping cart with AJAX
  • ✅ Multiple delivery addresses
  • ✅ Order history and tracking
  • ✅ Wishlist management
  • ✅ Loyalty points system
  • ✅ Product reviews and ratings

Store Features

  • ✅ Inventory management
  • ✅ Low stock alerts
  • ✅ Category management
  • ✅ Discounts and promotions
  • ✅ Order processing
  • ✅ Delivery management
  • ✅ Customer management
  • ✅ Sales reporting

Admin Features

  • ✅ Dashboard with analytics
  • ✅ Product management
  • ✅ Order management
  • ✅ User management
  • ✅ Settings configuration
  • ✅ Report generation
  • ✅ Supplier management
  • ✅ Purchase orders

🚀 Future Enhancements

  1. Mobile App: React Native or Flutter app
  2. Multi-store Support: Multiple store locations
  3. Real-time Tracking: GPS tracking for deliveries
  4. AI Recommendations: Personalized product suggestions
  5. Voice Search: Voice-enabled product search
  6. Subscription Service: Regular delivery subscriptions
  7. Loyalty Program: Enhanced rewards system
  8. Multi-language: Support for multiple languages
  9. Multi-currency: International payment support
  10. Analytics Dashboard: Advanced business intelligence

📝 Conclusion

This Online Grocery Store system provides a complete e-commerce solution specifically tailored for grocery businesses. With features like inventory management, delivery scheduling, loyalty programs, and comprehensive admin controls, it's perfect for local grocery stores looking to establish an online presence. The system is built with scalability in mind and can handle growing product catalogs and increasing customer bases.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper