Ecommerce Products
Ecommerce Products
UltraViolet Pro provides comprehensive product management features for creating, organizing, and displaying products in your online store.
Product Management
Creating Products
Basic Product Information
// Product creation example
$product = Product::create([
'name' => 'Premium Wireless Headphones',
'slug' => 'premium-wireless-headphones',
'description' => 'Experience premium sound quality with our wireless headphones...',
'short_description' => 'Premium wireless headphones with noise cancellation',
'sku' => 'PWH-001',
'price' => 299.99,
'compare_price' => 399.99,
'cost_price' => 150.00,
'stock_quantity' => 100,
'low_stock_threshold' => 10,
'weight' => 0.5,
'dimensions' => [
'length' => 20,
'width' => 18,
'height' => 8
],
'status' => 'active',
'featured' => true,
'meta_title' => 'Premium Wireless Headphones - Best Audio Quality',
'meta_description' => 'Shop premium wireless headphones with noise cancellation...'
]);
Product Categories
// Assign categories to product
$product->categories()->attach([1, 2, 3]); // Category IDs
// Create category hierarchy
$electronics = Category::create([
'name' => 'Electronics',
'slug' => 'electronics',
'description' => 'Electronic devices and accessories',
'parent_id' => null,
'sort_order' => 1
]);
$audio = Category::create([
'name' => 'Audio',
'slug' => 'audio',
'description' => 'Audio equipment and accessories',
'parent_id' => $electronics->id,
'sort_order' => 1
]);
$headphones = Category::create([
'name' => 'Headphones',
'slug' => 'headphones',
'description' => 'Headphones and earphones',
'parent_id' => $audio->id,
'sort_order' => 1
]);
Product Images
Uploading and Managing Images
// Upload product images
$product = Product::find(1);
// Primary image
$primaryImage = $product->images()->create([
'image_path' => 'products/headphones-main.jpg',
'alt_text' => 'Premium Wireless Headphones - Main View',
'sort_order' => 1,
'is_primary' => true
]);
// Additional images
$product->images()->create([
'image_path' => 'products/headphones-side.jpg',
'alt_text' => 'Premium Wireless Headphones - Side View',
'sort_order' => 2,
'is_primary' => false
]);
$product->images()->create([
'image_path' => 'products/headphones-detail.jpg',
'alt_text' => 'Premium Wireless Headphones - Detail View',
'sort_order' => 3,
'is_primary' => false
]);
Image Upload Form
<form action="{{ route('admin.products.images.store', $product) }}" method="POST" enctype="multipart/form-data">
@csrf
<div class="mb-3">
<label for="images" class="form-label">Product Images</label>
<input type="file" class="form-control" id="images" name="images[]" multiple accept="image/*">
<div class="form-text">Upload multiple images. First image will be set as primary.</div>
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="optimize_images" name="optimize_images" checked>
<label class="form-check-label" for="optimize_images">
Automatically optimize images
</label>
</div>
</div>
<button type="submit" class="btn btn-primary">Upload Images</button>
</form>
Product Variants
Creating Product Variants
// Product with variants (e.g., different colors and sizes)
$product = Product::create([
'name' => 'Classic T-Shirt',
'slug' => 'classic-t-shirt',
'description' => 'Comfortable cotton t-shirt...',
'sku' => 'CTS-BASE', // Base SKU
'price' => 24.99,
'status' => 'active'
]);
// Create variants
$variants = [
[
'name' => 'Classic T-Shirt - Black - Small',
'sku' => 'CTS-BLK-S',
'price' => 24.99,
'stock_quantity' => 50,
'attributes' => [
'color' => 'Black',
'size' => 'Small'
]
],
[
'name' => 'Classic T-Shirt - Black - Medium',
'sku' => 'CTS-BLK-M',
'price' => 24.99,
'stock_quantity' => 75,
'attributes' => [
'color' => 'Black',
'size' => 'Medium'
]
],
[
'name' => 'Classic T-Shirt - White - Small',
'sku' => 'CTS-WHT-S',
'price' => 24.99,
'stock_quantity' => 30,
'attributes' => [
'color' => 'White',
'size' => 'Small'
]
]
];
foreach ($variants as $variantData) {
$product->variants()->create($variantData);
}
Variant Selection Interface
<div class="product-variants">
<div class="variant-group mb-3">
<label class="form-label fw-bold">Color:</label>
<div class="variant-options d-flex gap-2">
<button type="button" class="btn btn-outline-secondary variant-option active"
data-variant="color" data-value="black">
<span class="color-swatch me-2" style="width: 20px; height: 20px; background-color: #000; border-radius: 50%; display: inline-block;"></span>
Black
</button>
<button type="button" class="btn btn-outline-secondary variant-option"
data-variant="color" data-value="white">
<span class="color-swatch me-2" style="width: 20px; height: 20px; background-color: #fff; border: 1px solid #ddd; border-radius: 50%; display: inline-block;"></span>
White
</button>
<button type="button" class="btn btn-outline-secondary variant-option"
data-variant="color" data-value="blue">
<span class="color-swatch me-2" style="width: 20px; height: 20px; background-color: #007bff; border-radius: 50%; display: inline-block;"></span>
Blue
</button>
</div>
</div>
<div class="variant-group mb-3">
<label class="form-label fw-bold">Size:</label>
<div class="variant-options d-flex gap-2">
<button type="button" class="btn btn-outline-secondary variant-option active"
data-variant="size" data-value="small">S</button>
<button type="button" class="btn btn-outline-secondary variant-option"
data-variant="size" data-value="medium">M</button>
<button type="button" class="btn btn-outline-secondary variant-option"
data-variant="size" data-value="large">L</button>
<button type="button" class="btn btn-outline-secondary variant-option"
data-variant="size" data-value="xlarge">XL</button>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const variantOptions = document.querySelectorAll('.variant-option');
let selectedVariants = {};
variantOptions.forEach(option => {
option.addEventListener('click', function() {
const variant = this.dataset.variant;
const value = this.dataset.value;
// Remove active class from siblings
this.parentNode.querySelectorAll('.variant-option').forEach(opt => {
opt.classList.remove('active');
});
// Add active class to clicked option
this.classList.add('active');
// Update selected variants
selectedVariants[variant] = value;
// Update product info based on selected variants
updateProductInfo(selectedVariants);
});
});
function updateProductInfo(variants) {
// Make AJAX request to get variant info
fetch(`/api/products/${productId}/variant`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
},
body: JSON.stringify(variants)
})
.then(response => response.json())
.then(data => {
if (data.variant) {
document.querySelector('.product-price .current-price').textContent = `$${data.variant.price}`;
document.querySelector('.product-sku').textContent = `SKU: ${data.variant.sku}`;
document.querySelector('.stock-status').textContent = data.variant.stock_quantity > 0
? `In Stock (${data.variant.stock_quantity} available)`
: 'Out of Stock';
}
});
}
});
</script>
Product Display
Product Grid Layout
<div class="row">
@foreach($products as $product)
<div class="col-lg-3 col-md-4 col-sm-6 mb-4">
<div class="card product-card h-100">
<div class="product-image-container">
<img src="{{ $product->primary_image->image_path ?? '/images/placeholder.jpg' }}"
class="card-img-top" alt="{{ $product->name }}">
@if($product->discount_percentage > 0)
<div class="discount-badge">
-{{ $product->discount_percentage }}%
</div>
@endif
<div class="product-actions">
<button class="btn btn-sm btn-outline-primary" onclick="quickView({{ $product->id }})">
<i class="bi bi-eye"></i>
</button>
<button class="btn btn-sm btn-outline-danger" onclick="addToWishlist({{ $product->id }})">
<i class="bi bi-heart"></i>
</button>
</div>
</div>
<div class="card-body d-flex flex-column">
<h6 class="card-title">{{ $product->name }}</h6>
<div class="product-rating mb-2">
<div class="stars">
@for($i = 1; $i <= 5; $i++)
<i class="bi bi-star{{ $i <= 4 ? '-fill' : '' }} text-warning"></i>
@endfor
</div>
<small class="text-muted">(4.5)</small>
</div>
<div class="product-price mb-3">
<span class="current-price fw-bold">${{ $product->price }}</span>
@if($product->compare_price)
<span class="original-price text-muted text-decoration-line-through ms-2">
${{ $product->compare_price }}
</span>
@endif
</div>
<div class="mt-auto">
@if($product->is_in_stock)
<button class="btn btn-primary w-100" onclick="addToCart({{ $product->id }})">
<i class="bi bi-cart-plus"></i> Add to Cart
</button>
@else
<button class="btn btn-secondary w-100" disabled>
Out of Stock
</button>
@endif
</div>
</div>
</div>
</div>
@endforeach
</div>
Product List Layout
<div class="product-list">
@foreach($products as $product)
<div class="product-item border-bottom py-3">
<div class="row align-items-center">
<div class="col-md-2">
<img src="{{ $product->primary_image->image_path ?? '/images/placeholder.jpg' }}"
class="img-fluid rounded" alt="{{ $product->name }}">
</div>
<div class="col-md-6">
<h5 class="product-title mb-1">
<a href="route-products.show-product-gt-slug.html" class="text-decoration-none">
{{ $product->name }}
</a>
</h5>
<p class="product-description text-muted mb-2">
{{ Str::limit($product->short_description, 100) }}
</p>
<div class="product-rating">
<div class="stars d-inline-block">
@for($i = 1; $i <= 5; $i++)
<i class="bi bi-star{{ $i <= 4 ? '-fill' : '' }} text-warning"></i>
@endfor
</div>
<small class="text-muted ms-2">(4.5) 128 reviews</small>
</div>
</div>
<div class="col-md-2">
<div class="product-price text-center">
<div class="current-price fw-bold">${{ $product->price }}</div>
@if($product->compare_price)
<div class="original-price text-muted text-decoration-line-through">
${{ $product->compare_price }}
</div>
@endif
</div>
</div>
<div class="col-md-2">
<div class="product-actions text-center">
@if($product->is_in_stock)
<button class="btn btn-primary btn-sm mb-2" onclick="addToCart({{ $product->id }})">
<i class="bi bi-cart-plus"></i> Add to Cart
</button>
@else
<button class="btn btn-secondary btn-sm mb-2" disabled>
Out of Stock
</button>
@endif
<div>
<button class="btn btn-outline-secondary btn-sm me-1" onclick="addToWishlist({{ $product->id }})">
<i class="bi bi-heart"></i>
</button>
<button class="btn btn-outline-secondary btn-sm" onclick="quickView({{ $product->id }})">
<i class="bi bi-eye"></i>
</button>
</div>
</div>
</div>
</div>
</div>
@endforeach
</div>
Product Search and Filtering
Search Interface
<div class="product-search mb-4">
<div class="row">
<div class="col-md-8">
<div class="input-group">
<input type="text" class="form-control" id="productSearch"
placeholder="Search products..." value="{{ request('search') }}">
<button class="btn btn-outline-secondary" type="button" onclick="searchProducts()">
<i class="bi bi-search"></i>
</button>
</div>
</div>
<div class="col-md-4">
<select class="form-select" id="sortBy" onchange="sortProducts()">
<option value="name_asc" {{ request('sort') == 'name_asc' ? 'selected' : '' }}>Name A-Z</option>
<option value="name_desc" {{ request('sort') == 'name_desc' ? 'selected' : '' }}>Name Z-A</option>
<option value="price_asc" {{ request('sort') == 'price_asc' ? 'selected' : '' }}>Price Low to High</option>
<option value="price_desc" {{ request('sort') == 'price_desc' ? 'selected' : '' }}>Price High to Low</option>
<option value="newest" {{ request('sort') == 'newest' ? 'selected' : '' }}>Newest First</option>
<option value="popular" {{ request('sort') == 'popular' ? 'selected' : '' }}>Most Popular</option>
</select>
</div>
</div>
</div>
Filter Sidebar
<div class="row">
<div class="col-md-3">
<div class="filter-sidebar">
<h5>Filters</h5>
<!-- Price Range -->
<div class="filter-group mb-4">
<h6>Price Range</h6>
<div class="price-range">
<input type="range" class="form-range" id="priceMin" min="0" max="1000" value="0">
<input type="range" class="form-range" id="priceMax" min="0" max="1000" value="1000">
<div class="price-display">
$<span id="minPrice">0</span> - $<span id="maxPrice">1000</span>
</div>
</div>
</div>
<!-- Categories -->
<div class="filter-group mb-4">
<h6>Categories</h6>
<div class="category-filters">
@foreach($categories as $category)
<div class="form-check">
<input class="form-check-input" type="checkbox"
id="category{{ $category->id }}"
value="{{ $category->id }}"
{{ in_array($category->id, request('categories', [])) ? 'checked' : '' }}>
<label class="form-check-label" for="category{{ $category->id }}">
{{ $category->name }}
<span class="text-muted">({{ $category->products_count }})</span>
</label>
</div>
@endforeach
</div>
</div>
<!-- Brands -->
<div class="filter-group mb-4">
<h6>Brands</h6>
<div class="brand-filters">
@foreach($brands as $brand)
<div class="form-check">
<input class="form-check-input" type="checkbox"
id="brand{{ $brand->id }}"
value="{{ $brand->id }}"
{{ in_array($brand->id, request('brands', [])) ? 'checked' : '' }}>
<label class="form-check-label" for="brand{{ $brand->id }}">
{{ $brand->name }}
<span class="text-muted">({{ $brand->products_count }})</span>
</label>
</div>
@endforeach
</div>
</div>
<!-- Availability -->
<div class="filter-group mb-4">
<h6>Availability</h6>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="inStock"
{{ request('in_stock') ? 'checked' : '' }}>
<label class="form-check-label" for="inStock">
In Stock Only
</label>
</div>
</div>
<button class="btn btn-primary w-100" onclick="applyFilters()">Apply Filters</button>
<button class="btn btn-outline-secondary w-100 mt-2" onclick="clearFilters()">Clear All</button>
</div>
</div>
<div class="col-md-9">
<!-- Product results will be displayed here -->
<div id="productResults">
@include('products.partials.product-grid', ['products' => $products])
</div>
</div>
</div>
Inventory Management
Stock Tracking
// Update stock quantities
public function updateStock($productId, $quantity, $operation = 'set')
{
$product = Product::findOrFail($productId);
switch ($operation) {
case 'add':
$product->increment('stock_quantity', $quantity);
break;
case 'subtract':
$product->decrement('stock_quantity', $quantity);
break;
case 'set':
default:
$product->update(['stock_quantity' => $quantity]);
break;
}
// Check for low stock alert
if ($product->stock_quantity <= $product->low_stock_threshold) {
$this->sendLowStockAlert($product);
}
return $product;
}
// Low stock alert
private function sendLowStockAlert($product)
{
// Send notification to admin
Notification::route('mail', config('mail.admin_email'))
->notify(new LowStockAlert($product));
}
Bulk Operations
<!-- Bulk product operations -->
<div class="bulk-actions mb-3">
<div class="row align-items-center">
<div class="col-md-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="selectAll">
<label class="form-check-label" for="selectAll">
Select All Products
</label>
</div>
</div>
<div class="col-md-6">
<div class="bulk-actions-buttons">
<select class="form-select d-inline-block w-auto me-2" id="bulkAction">
<option value="">Bulk Actions</option>
<option value="activate">Activate</option>
<option value="deactivate">Deactivate</option>
<option value="feature">Mark as Featured</option>
<option value="unfeature">Remove from Featured</option>
<option value="delete">Delete</option>
</select>
<button class="btn btn-primary" onclick="executeBulkAction()">Apply</button>
</div>
</div>
</div>
</div>
This comprehensive product management system provides everything needed to run a professional ecommerce store with advanced features for product organization, display, and inventory management.