Files
hub/app/Http/Controllers/Seller/ProductImageController.php
kelly 6688bbf8a1 fix: product image upload improvements
- 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
2025-12-11 18:18:51 -07:00

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]);
}
}