- Change max images from 8 to 6
- Fix drag and drop with proper event handlers (prevent, stop propagation)
- Stay on page after upload instead of redirecting
- Use proper storage path: businesses/{slug}/brands/{slug}/products/{sku}/images/
- Return image URLs in upload response for dynamic UI update
- Change button text from 'Replace Image' to 'Add Image' for clarity
- Maintain validation: JPG/PNG, max 2MB, 750x384px minimum
178 lines
5.7 KiB
PHP
178 lines
5.7 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Seller;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Business;
|
|
use App\Models\Product;
|
|
use App\Models\ProductImage;
|
|
use App\Traits\FileStorageHelper;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
|
class ProductImageController extends Controller
|
|
{
|
|
use FileStorageHelper;
|
|
|
|
/**
|
|
* Upload a new product image
|
|
*/
|
|
public function upload(Request $request, Business $business, Product $product)
|
|
{
|
|
// CRITICAL: Ensure product belongs to this business through brand
|
|
$product = Product::whereHas('brand', function ($query) use ($business) {
|
|
$query->where('business_id', $business->id);
|
|
})->findOrFail($product->id);
|
|
|
|
// Validate image
|
|
$request->validate([
|
|
'image' => 'required|image|mimes:jpeg,jpg,png|max:2048|dimensions:min_width=750,min_height=384', // 2MB max, 750x384 min
|
|
]);
|
|
|
|
// Check if product already has 6 images
|
|
if ($product->images()->count() >= 6) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Maximum of 6 images allowed per product',
|
|
], 422);
|
|
}
|
|
|
|
// Build proper storage path: businesses/{business_slug}/brands/{brand_slug}/products/{sku}/images/
|
|
$brand = $product->brand;
|
|
$storagePath = sprintf(
|
|
'businesses/%s/brands/%s/products/%s/images',
|
|
$business->slug,
|
|
$brand->slug,
|
|
$product->sku
|
|
);
|
|
|
|
// Store the image with proper path
|
|
$path = $this->storeFile($request->file('image'), $storagePath);
|
|
|
|
// Determine if this should be the primary image (first one)
|
|
$isPrimary = $product->images()->count() === 0;
|
|
|
|
// If setting as primary, unset other primary images
|
|
if ($isPrimary) {
|
|
$product->images()->update(['is_primary' => false]);
|
|
}
|
|
|
|
// Create the image record
|
|
$image = $product->images()->create([
|
|
'path' => $path,
|
|
'is_primary' => $isPrimary,
|
|
'sort_order' => $product->images()->max('sort_order') + 1,
|
|
]);
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'image' => [
|
|
'id' => $image->id,
|
|
'path' => $image->path,
|
|
'is_primary' => $image->is_primary,
|
|
'url' => route('image.product', ['product' => $product->hashid, 'width' => 400]),
|
|
'thumb_url' => route('image.product', ['product' => $product->hashid, 'width' => 80]),
|
|
],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Delete a product image
|
|
*/
|
|
public function delete(Business $business, Product $product, ProductImage $image)
|
|
{
|
|
// CRITICAL: Ensure product belongs to this business through brand
|
|
$product = Product::whereHas('brand', function ($query) use ($business) {
|
|
$query->where('business_id', $business->id);
|
|
})->findOrFail($product->id);
|
|
|
|
// Ensure image belongs to this product
|
|
if ($image->product_id !== $product->id) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Image not found',
|
|
], 404);
|
|
}
|
|
|
|
// Delete the file from storage using trait method
|
|
$this->deleteFile($image->path);
|
|
|
|
// If deleting primary image, set next image as primary
|
|
if ($image->is_primary) {
|
|
$nextImage = $product->images()
|
|
->where('id', '!=', $image->id)
|
|
->orderBy('sort_order')
|
|
->first();
|
|
|
|
if ($nextImage) {
|
|
$nextImage->update(['is_primary' => true]);
|
|
}
|
|
}
|
|
|
|
$image->delete();
|
|
|
|
return response()->json(['success' => true]);
|
|
}
|
|
|
|
/**
|
|
* Reorder product images
|
|
*/
|
|
public function reorder(Request $request, Business $business, Product $product)
|
|
{
|
|
// CRITICAL: Ensure product belongs to this business through brand
|
|
$product = Product::whereHas('brand', function ($query) use ($business) {
|
|
$query->where('business_id', $business->id);
|
|
})->findOrFail($product->id);
|
|
|
|
$request->validate([
|
|
'order' => 'required|array',
|
|
'order.*' => 'required|integer|exists:product_images,id',
|
|
]);
|
|
|
|
$order = $request->input('order');
|
|
|
|
// Update sort order and set first image as primary
|
|
foreach ($order as $index => $imageId) {
|
|
$image = ProductImage::where('id', $imageId)
|
|
->where('product_id', $product->id)
|
|
->first();
|
|
|
|
if ($image) {
|
|
$image->update([
|
|
'sort_order' => $index,
|
|
'is_primary' => $index === 0, // First image is primary
|
|
]);
|
|
}
|
|
}
|
|
|
|
return response()->json(['success' => true]);
|
|
}
|
|
|
|
/**
|
|
* Set an image as primary
|
|
*/
|
|
public function setPrimary(Business $business, Product $product, ProductImage $image)
|
|
{
|
|
// CRITICAL: Ensure product belongs to this business through brand
|
|
$product = Product::whereHas('brand', function ($query) use ($business) {
|
|
$query->where('business_id', $business->id);
|
|
})->findOrFail($product->id);
|
|
|
|
// Ensure image belongs to this product
|
|
if ($image->product_id !== $product->id) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Image not found',
|
|
], 404);
|
|
}
|
|
|
|
// Unset all primary flags for this product
|
|
$product->images()->update(['is_primary' => false]);
|
|
|
|
// Set this image as primary
|
|
$image->update(['is_primary' => true]);
|
|
|
|
return response()->json(['success' => true]);
|
|
}
|
|
}
|