diff --git a/IMAGE_UPLOAD_FIX.md b/IMAGE_UPLOAD_FIX.md
new file mode 100644
index 0000000..722d84d
--- /dev/null
+++ b/IMAGE_UPLOAD_FIX.md
@@ -0,0 +1,386 @@
+# Image Upload & Loading Fix - Brand Master
+
+## Problem Summary
+
+Images were not loading correctly in the Brand Master application due to:
+
+1. **URL Resolution Issue**: Images stored with relative paths (`/uploads/products/...`) were resolving to the frontend domain instead of the backend
+2. **Missing Category Upload Endpoint**: No dedicated endpoint for category image uploads
+3. **Ingress Routing**: No route from frontend domain to backend for serving static uploads
+4. **No Fallback Handling**: Broken images displayed when URLs were invalid
+
+## Solutions Implemented
+
+### 1. Backend Changes
+
+#### a. Updated Settings Configuration
+**File**: `backend/app/config.py`
+- Added `backend_url` setting to store the backend URL
+- This is set via `BACKEND_URL` environment variable in Kubernetes
+
+```python
+backend_url: str = "http://localhost:8000"
+```
+
+#### b. Enhanced Upload Utility
+**File**: `backend/app/utils.py`
+- Modified `save_upload_file()` to accept `backend_url` parameter
+- Now returns full URLs (e.g., `https://api-brand-master.dvirlabs.com/uploads/products/...`) instead of relative paths
+- Maintains backward compatibility with relative paths if `backend_url` not provided
+
+```python
+def save_upload_file(upload_file, folder: str = "products", backend_url: str = None) -> str:
+ # ... saves file ...
+ relative_path = f"/uploads/{folder}/{unique_filename}"
+ if backend_url:
+ backend_url = backend_url.rstrip('/')
+ return f"{backend_url}{relative_path}"
+ return relative_path
+```
+
+#### c. Updated Product Upload Endpoints
+**File**: `backend/app/routers/products.py`
+- Updated `/api/products/upload-image` to use `backend_url` from settings
+- Updated `/api/products/upload-images` to use `backend_url` from settings
+- Images now return full URLs immediately
+
+#### d. Added Category Upload Endpoint
+**File**: `backend/app/routers/categories.py`
+- Added new endpoint: `POST /api/categories/upload-image`
+- Validates image file types (jpeg, png, jpg, webp, gif)
+- Returns full URL with backend domain
+- Saves to `uploads/categories/` folder
+
+### 2. Frontend Changes
+
+#### a. Updated Admin Panel Image Upload
+**File**: `frontend/src/pages/Admin.jsx`
+
+**Product Images**:
+- Removed URL building logic (backend returns full URLs)
+- Simplified `handleImageUpload()` function
+- Backend now handles URL generation
+
+**Category Images**:
+- Changed from `/products/upload-image` to `/categories/upload-image`
+- Removed URL building logic
+- Directly uses backend-returned full URLs
+
+#### b. Added Fallback Image Handling
+**File**: `frontend/src/components/ProductCard.jsx`
+- Added error state tracking with `useState`
+- Handles image load failures with `onError` handler
+- Falls back to placeholder image if product image fails to load
+- Fallback: `https://via.placeholder.com/400x400?text={product-name}`
+
+**File**: `frontend/src/components/CategoryCard.jsx`
+- Added error state tracking with `useState`
+- Handles image load failures with `onError` handler
+- Falls back to placeholder image if category image fails to load
+- Fallback: `https://via.placeholder.com/300x300?text={category-name}`
+
+### 3. Kubernetes/Helm Changes
+
+#### a. Updated Frontend Ingress
+**File**: `charts/brand-master-chart/templates/frontend-ingress.yaml`
+- Added `/uploads` path routing to backend service
+- This provides backward compatibility for any existing relative URLs
+- Routes `/uploads/*` requests from frontend domain to backend service
+- Ensures images work even if old relative URLs exist in database
+
+**Path Priority**:
+1. `/uploads` → Backend Service (for images)
+2. `/` → Frontend Service (for everything else)
+
+#### b. Persistence Configuration
+**Already Configured** in `manifests/brand-master/values.yaml`:
+- PersistentVolumeClaim enabled for backend
+- Storage: 15Gi on NFS storage class
+- Mount path: `/app/uploads`
+- Ensures images persist across pod restarts
+
+## Deployment Instructions
+
+### 1. Backend Deployment
+
+```bash
+cd ~/OneDrive/Desktop/gitea/brand-master
+
+# Build new backend image
+docker build -t harbor.dvirlabs.com/my-apps/brand-master-backend:latest -f backend/Dockerfile backend/
+
+# Push to Harbor
+docker push harbor.dvirlabs.com/my-apps/brand-master-backend:latest
+```
+
+### 2. Frontend Deployment
+
+```bash
+cd ~/OneDrive/Desktop/gitea/brand-master
+
+# Build new frontend image
+docker build -t harbor.dvirlabs.com/my-apps/brand-master-frontend:latest -f frontend/Dockerfile frontend/
+
+# Push to Harbor
+docker push harbor.dvirlabs.com/my-apps/brand-master-frontend:latest
+```
+
+### 3. Update Helm Deployment
+
+```bash
+cd ~/OneDrive/Desktop/gitea/my-apps
+
+# Update image tags in values.yaml if using specific tags
+# Or use :latest for auto-pull
+
+# Upgrade Helm release
+helm upgrade brand-master charts/brand-master-chart \
+ -f manifests/brand-master/values.yaml \
+ -n my-apps
+```
+
+### 4. Verify Deployment
+
+```bash
+# Check pods are running
+kubectl get pods -n my-apps -l app.kubernetes.io/name=brand-master
+
+# Check backend logs
+kubectl logs -n my-apps -l app.kubernetes.io/component=backend --tail=50
+
+# Check frontend logs
+kubectl logs -n my-apps -l app.kubernetes.io/component=frontend --tail=50
+
+# Verify PVC is mounted
+kubectl describe pod -n my-apps -l app.kubernetes.io/component=backend | grep uploads
+```
+
+## Testing Instructions
+
+### 1. Test Category Image Upload
+
+1. Login as admin: https://brand-master.dvirlabs.com/admin
+2. Go to "Categories" tab
+3. Create or edit a category
+4. Upload an image
+5. **Expected**: Image preview shows immediately
+6. Save category
+7. **Expected**: Image displays on home page category cards
+8. Refresh page
+9. **Expected**: Image still loads correctly
+
+### 2. Test Product Image Upload
+
+1. Login as admin
+2. Go to "Products" tab
+3. Create or edit a product
+4. Upload one or more images
+5. **Expected**: Image previews show immediately in upload section
+6. Save product
+7. **Expected**: Product displays correctly in products list
+8. View product detail page
+9. **Expected**: All images load and gallery works
+10. Refresh page
+11. **Expected**: All images still load correctly
+
+### 3. Test Image Persistence
+
+1. Upload some product/category images
+2. Restart backend pod:
+ ```bash
+ kubectl rollout restart deployment -n my-apps brand-master-backend
+ ```
+3. Wait for pod to be ready
+4. **Expected**: All previously uploaded images still load correctly
+
+### 4. Test Fallback Handling
+
+1. Manually break an image URL in the database (or edit in admin with invalid URL)
+2. View product/category with broken image
+3. **Expected**: Placeholder image displays instead of broken image icon
+4. No console errors about failed image loads
+
+### 5. Verify URLs in Database
+
+```bash
+# Connect to database pod
+kubectl exec -it -n my-apps brand-master-db-0 -- psql -U brand_master_user -d brand_master_db
+
+# Check product image URLs
+SELECT id, name, images FROM product LIMIT 5;
+
+# Check category image URLs
+SELECT id, name, image FROM category;
+```
+
+**Expected URL Format**:
+- New images: `https://api-brand-master.dvirlabs.com/uploads/products/...`
+- Old images (if any): `/uploads/products/...` (will still work via ingress routing)
+
+### 6. Test Direct Image Access
+
+1. Upload an image and note the URL returned
+2. Copy the full URL
+3. Open in new browser tab
+4. **Expected**: Image loads directly
+5. Test both domains:
+ - `https://api-brand-master.dvirlabs.com/uploads/...` (backend)
+ - `https://brand-master.dvirlabs.com/uploads/...` (frontend → routed to backend)
+
+## Architecture Overview
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ Ingress (Traefik) │
+└─────────────────────────────────────────────────────────────┘
+ │ │
+ │ api-brand-master.dvirlabs.com │ brand-master.dvirlabs.com
+ │ │
+ ▼ ▼
+┌─────────────────┐ ┌─────────────────┐
+│ Backend Svc │ │ Frontend Svc │
+│ (Port 8000) │ │ (Port 80) │
+└─────────────────┘ └─────────────────┘
+ │ │
+ │ ├── /uploads → Backend Svc
+ │ └── /* → Frontend Pod
+ ▼
+┌─────────────────┐
+│ Backend Pod │
+│ - FastAPI │
+│ - Static Files │
+│ at /uploads │
+└─────────────────┘
+ │
+ ▼
+┌─────────────────┐
+│ PVC (15Gi) │
+│ /app/uploads │
+└─────────────────┘
+```
+
+## Image Flow
+
+### Upload Flow:
+1. Admin uploads image via frontend form
+2. Frontend sends file to `/api/products/upload-image` or `/api/categories/upload-image`
+3. Backend saves file to `uploads/{folder}/{uuid}{ext}` on PVC
+4. Backend returns full URL: `https://api-brand-master.dvirlabs.com/uploads/{folder}/{filename}`
+5. Frontend stores full URL in database
+
+### Display Flow (New Images):
+1. Frontend renders ``
+2. Browser requests image from backend domain
+3. Ingress routes to Backend Service
+4. FastAPI serves file via StaticFiles mount
+5. Image displays
+
+### Display Flow (Old Relative URLs - Backup):
+1. Frontend renders `
`
+2. Browser requests from frontend domain
+3. Ingress routes `/uploads/*` to Backend Service
+4. FastAPI serves file
+5. Image displays
+
+## Troubleshooting
+
+### Issue: Images still not loading
+
+**Check 1**: Verify backend URL is set correctly
+```bash
+kubectl get pod -n my-apps -l app.kubernetes.io/component=backend -o jsonpath='{.items[0].spec.containers[0].env[?(@.name=="BACKEND_URL")].value}'
+```
+Should return: `https://api-brand-master.dvirlabs.com`
+
+**Check 2**: Verify StaticFiles is mounted
+```bash
+kubectl exec -n my-apps -l app.kubernetes.io/component=backend -- ls -la /app/uploads
+```
+
+**Check 3**: Verify ingress routes
+```bash
+kubectl get ingress -n my-apps brand-master-frontend -o yaml
+```
+Should show `/uploads` path routing to backend service.
+
+**Check 4**: Test direct backend access
+```bash
+curl -I https://api-brand-master.dvirlabs.com/uploads/products/some-file.jpg
+```
+
+### Issue: Images disappear after pod restart
+
+**Check**: PVC is properly mounted
+```bash
+kubectl get pvc -n my-apps
+kubectl describe pvc -n my-apps brand-master-uploads-pvc
+```
+
+Verify:
+- STATUS: Bound
+- STORAGECLASS: nfs-client
+- CAPACITY: 15Gi
+
+### Issue: Upload fails with 500 error
+
+**Check backend logs**:
+```bash
+kubectl logs -n my-apps -l app.kubernetes.io/component=backend --tail=100
+```
+
+Common causes:
+- Permissions issue on /app/uploads
+- PVC not mounted
+- Disk space full
+
+## Summary of Files Changed
+
+### Backend (Code Repo)
+- ✅ `backend/app/config.py` - Added backend_url setting
+- ✅ `backend/app/utils.py` - Updated save_upload_file to return full URLs
+- ✅ `backend/app/routers/products.py` - Updated to use backend_url
+- ✅ `backend/app/routers/categories.py` - Added upload endpoint
+
+### Frontend (Code Repo)
+- ✅ `frontend/src/pages/Admin.jsx` - Removed URL building, use backend URLs
+- ✅ `frontend/src/components/ProductCard.jsx` - Added fallback handling
+- ✅ `frontend/src/components/CategoryCard.jsx` - Added fallback handling
+
+### Kubernetes (GitOps Repo)
+- ✅ `charts/brand-master-chart/templates/frontend-ingress.yaml` - Added /uploads routing
+
+## Next Steps
+
+1. Deploy backend changes
+2. Deploy frontend changes
+3. Update Helm deployment with new ingress config
+4. Test all image upload scenarios
+5. Verify images persist across restarts
+6. Monitor logs for any errors
+
+## Rollback Plan
+
+If issues occur after deployment:
+
+```bash
+# Rollback Helm release
+helm rollback brand-master -n my-apps
+
+# Or rollback individual components
+kubectl rollout undo deployment/brand-master-backend -n my-apps
+kubectl rollout undo deployment/brand-master-frontend -n my-apps
+```
+
+## Environment Variables Reference
+
+Required in backend deployment:
+
+```yaml
+env:
+ BACKEND_URL: "https://api-brand-master.dvirlabs.com" # Must be set!
+ FRONTEND_URL: "https://brand-master.dvirlabs.com"
+ PYTHONUNBUFFERED: "1"
+ # ... other env vars ...
+```
+
+Already configured in `manifests/brand-master/values.yaml`.
diff --git a/apply-migration.bat b/apply-migration.bat
new file mode 100644
index 0000000..9a59df6
--- /dev/null
+++ b/apply-migration.bat
@@ -0,0 +1,29 @@
+@echo off
+REM Apply database migration 005 - Add password reset fields
+
+echo Getting database pod name...
+for /f "delims=" %%i in ('kubectl get pod -n my-apps -l app.kubernetes.io/component=db -o jsonpath^="{.items[0].metadata.name}"') do set DB_POD=%%i
+
+if "%DB_POD%"=="" (
+ echo ❌ Database pod not found
+ exit /b 1
+)
+
+echo 📦 Database pod: %DB_POD%
+echo 📝 Applying migration 005_add_password_reset_fields.sql...
+echo.
+
+REM Copy migration file to pod
+kubectl cp backend/migrations/005_add_password_reset_fields.sql my-apps/%DB_POD%:/tmp/migration.sql
+
+REM Execute migration
+kubectl exec -n my-apps %DB_POD% -- psql -U brand_master_user -d brand_master_db -f /tmp/migration.sql
+
+echo.
+echo ✅ Migration applied successfully!
+echo.
+echo 🔄 Restarting backend pod to pick up changes...
+kubectl delete pod -n my-apps -l app.kubernetes.io/component=backend
+
+echo.
+echo ✅ Done! Backend will restart with updated schema.
diff --git a/apply-migration.sh b/apply-migration.sh
new file mode 100644
index 0000000..6ec289c
--- /dev/null
+++ b/apply-migration.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+# Apply database migration 005 - Add password reset fields
+
+# Get the database pod name
+DB_POD=$(kubectl get pod -n my-apps -l app.kubernetes.io/component=db -o jsonpath='{.items[0].metadata.name}')
+
+if [ -z "$DB_POD" ]; then
+ echo "❌ Database pod not found"
+ exit 1
+fi
+
+echo "📦 Database pod: $DB_POD"
+echo "📝 Applying migration 005_add_password_reset_fields.sql..."
+echo ""
+
+# Copy migration file to pod
+kubectl cp backend/migrations/005_add_password_reset_fields.sql my-apps/$DB_POD:/tmp/migration.sql
+
+# Execute migration
+kubectl exec -n my-apps $DB_POD -- psql -U brand_master_user -d brand_master_db -f /tmp/migration.sql
+
+echo ""
+echo "✅ Migration applied successfully!"
+echo ""
+echo "🔄 Restarting backend pod to pick up changes..."
+kubectl delete pod -n my-apps -l app.kubernetes.io/component=backend
+
+echo ""
+echo "✅ Done! Backend will restart with updated schema."
diff --git a/backend/Dockerfile b/backend/Dockerfile
index 4f349b1..26a4ee4 100644
--- a/backend/Dockerfile
+++ b/backend/Dockerfile
@@ -28,4 +28,3 @@ ENV PYTHONUNBUFFERED=1
# Run the application
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
-
diff --git a/backend/app/__pycache__/config.cpython-314.pyc b/backend/app/__pycache__/config.cpython-314.pyc
index f6d7598..58a4980 100644
Binary files a/backend/app/__pycache__/config.cpython-314.pyc and b/backend/app/__pycache__/config.cpython-314.pyc differ
diff --git a/backend/app/__pycache__/main.cpython-314.pyc b/backend/app/__pycache__/main.cpython-314.pyc
index a8dabf1..2c69648 100644
Binary files a/backend/app/__pycache__/main.cpython-314.pyc and b/backend/app/__pycache__/main.cpython-314.pyc differ
diff --git a/backend/app/__pycache__/utils.cpython-314.pyc b/backend/app/__pycache__/utils.cpython-314.pyc
index 47b6246..1f90343 100644
Binary files a/backend/app/__pycache__/utils.cpython-314.pyc and b/backend/app/__pycache__/utils.cpython-314.pyc differ
diff --git a/backend/app/config.py b/backend/app/config.py
index 2aa7ca2..97e2b27 100644
--- a/backend/app/config.py
+++ b/backend/app/config.py
@@ -8,6 +8,7 @@ class Settings(BaseSettings):
jwt_algorithm: str = "HS256"
access_token_expire_minutes: int = 30
frontend_url: str = "http://localhost:5173"
+ backend_url: str = "http://localhost:8000"
# Admin user credentials (created on first startup)
admin_email: str = "admin@brandmaster.com"
diff --git a/backend/app/models/__pycache__/user.cpython-314.pyc b/backend/app/models/__pycache__/user.cpython-314.pyc
index b5c6025..f6b2c60 100644
Binary files a/backend/app/models/__pycache__/user.cpython-314.pyc and b/backend/app/models/__pycache__/user.cpython-314.pyc differ
diff --git a/backend/app/routers/__pycache__/auth.cpython-314.pyc b/backend/app/routers/__pycache__/auth.cpython-314.pyc
index e035c85..454bda1 100644
Binary files a/backend/app/routers/__pycache__/auth.cpython-314.pyc and b/backend/app/routers/__pycache__/auth.cpython-314.pyc differ
diff --git a/backend/app/routers/__pycache__/categories.cpython-314.pyc b/backend/app/routers/__pycache__/categories.cpython-314.pyc
index a46a785..9dbe9c3 100644
Binary files a/backend/app/routers/__pycache__/categories.cpython-314.pyc and b/backend/app/routers/__pycache__/categories.cpython-314.pyc differ
diff --git a/backend/app/routers/__pycache__/products.cpython-314.pyc b/backend/app/routers/__pycache__/products.cpython-314.pyc
index 09e390c..a852b37 100644
Binary files a/backend/app/routers/__pycache__/products.cpython-314.pyc and b/backend/app/routers/__pycache__/products.cpython-314.pyc differ
diff --git a/backend/app/routers/__pycache__/users.cpython-314.pyc b/backend/app/routers/__pycache__/users.cpython-314.pyc
index 72ea1dd..b0fd3b1 100644
Binary files a/backend/app/routers/__pycache__/users.cpython-314.pyc and b/backend/app/routers/__pycache__/users.cpython-314.pyc differ
diff --git a/backend/app/routers/categories.py b/backend/app/routers/categories.py
index c9e92d8..7e09560 100644
--- a/backend/app/routers/categories.py
+++ b/backend/app/routers/categories.py
@@ -1,10 +1,12 @@
-from fastapi import APIRouter, Depends, HTTPException
+from fastapi import APIRouter, Depends, HTTPException, UploadFile, File
from sqlalchemy.orm import Session
from typing import List
from app.database.database import get_db
from app.models import Category, User
from app.schemas.category import CategoryCreate, CategoryResponse, CategoryUpdate
from app.services.auth import get_current_admin_user
+from app.utils import save_upload_file
+from app.config import settings
router = APIRouter(prefix="/api/categories", tags=["categories"])
@@ -69,3 +71,25 @@ def delete_category(
db.delete(category)
db.commit()
return {"message": "Category deleted successfully"}
+
+
+@router.post("/upload-image")
+async def upload_category_image(
+ file: UploadFile = File(...),
+ admin: User = Depends(get_current_admin_user),
+):
+ """Upload a category image and return the URL"""
+ # Validate file type
+ allowed_types = ["image/jpeg", "image/png", "image/jpg", "image/webp", "image/gif"]
+ if file.content_type not in allowed_types:
+ raise HTTPException(
+ status_code=400,
+ detail=f"File type {file.content_type} not allowed. Allowed types: {', '.join(allowed_types)}"
+ )
+
+ try:
+ # Save file and get full URL
+ file_path = save_upload_file(file, folder="categories", backend_url=settings.backend_url)
+ return {"url": file_path, "message": "Image uploaded successfully"}
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=f"Error uploading file: {str(e)}")
diff --git a/backend/app/routers/products.py b/backend/app/routers/products.py
index b5ef5a4..f314829 100644
--- a/backend/app/routers/products.py
+++ b/backend/app/routers/products.py
@@ -20,6 +20,7 @@ from app.services.product import (
)
from app.services.auth import get_current_admin_user
from app.utils import save_upload_file, generate_slug
+from app.config import settings
router = APIRouter(prefix="/api/products", tags=["products"])
@@ -152,7 +153,7 @@ async def upload_product_image(
try:
# Save file and get path
- file_path = save_upload_file(file, folder="products")
+ file_path = save_upload_file(file, folder="products", backend_url=settings.backend_url)
# Return full URL
return {"url": file_path, "message": "Image uploaded successfully"}
except Exception as e:
@@ -175,7 +176,7 @@ async def upload_multiple_images(
continue
try:
- file_path = save_upload_file(file, folder="products")
+ file_path = save_upload_file(file, folder="products", backend_url=settings.backend_url)
uploaded_urls.append(file_path)
except Exception as e:
errors.append(f"File {file.filename}: {str(e)}")
diff --git a/backend/app/schemas/__pycache__/user.cpython-314.pyc b/backend/app/schemas/__pycache__/user.cpython-314.pyc
index b79b1fc..450ef46 100644
Binary files a/backend/app/schemas/__pycache__/user.cpython-314.pyc and b/backend/app/schemas/__pycache__/user.cpython-314.pyc differ
diff --git a/backend/app/services/__pycache__/auth.cpython-314.pyc b/backend/app/services/__pycache__/auth.cpython-314.pyc
index 0bfafeb..1a25e92 100644
Binary files a/backend/app/services/__pycache__/auth.cpython-314.pyc and b/backend/app/services/__pycache__/auth.cpython-314.pyc differ
diff --git a/backend/app/utils.py b/backend/app/utils.py
index c8f0f41..8499283 100644
--- a/backend/app/utils.py
+++ b/backend/app/utils.py
@@ -13,8 +13,8 @@ def generate_slug(text: str) -> str:
slug = re.sub(r'^-+|-+$', '', slug)
return slug
-def save_upload_file(upload_file, folder: str = "products") -> str:
- """Save uploaded file and return the file path"""
+def save_upload_file(upload_file, folder: str = "products", backend_url: str = None) -> str:
+ """Save uploaded file and return the file URL"""
# Create uploads directory if it doesn't exist
upload_dir = Path("uploads") / folder
upload_dir.mkdir(parents=True, exist_ok=True)
@@ -28,5 +28,10 @@ def save_upload_file(upload_file, folder: str = "products") -> str:
with open(file_path, "wb") as buffer:
buffer.write(upload_file.file.read())
- # Return relative path for URL
- return f"/uploads/{folder}/{unique_filename}"
+ # Return full URL if backend_url provided, otherwise relative path
+ relative_path = f"/uploads/{folder}/{unique_filename}"
+ if backend_url:
+ # Remove trailing slash from backend_url if present
+ backend_url = backend_url.rstrip('/')
+ return f"{backend_url}{relative_path}"
+ return relative_path
diff --git a/frontend/Dockerfile b/frontend/Dockerfile
index 0d3b02f..c7fab33 100644
--- a/frontend/Dockerfile
+++ b/frontend/Dockerfile
@@ -67,3 +67,4 @@ EXPOSE 80
# Start nginx
CMD ["nginx", "-g", "daemon off;"]
+
diff --git a/frontend/src/components/CategoryCard.jsx b/frontend/src/components/CategoryCard.jsx
index 06c8ba8..838d939 100644
--- a/frontend/src/components/CategoryCard.jsx
+++ b/frontend/src/components/CategoryCard.jsx
@@ -1,12 +1,18 @@
-import React from 'react'
+import React, { useState } from 'react'
import { Link } from 'react-router-dom'
export default function CategoryCard({ category }) {
- const categoryImage = category.image || `https://via.placeholder.com/300x300?text=${category.name}`
+ const [imageError, setImageError] = useState(false)
+ const fallbackImage = `https://via.placeholder.com/300x300?text=${encodeURIComponent(category.name)}`
+ const categoryImage = (category.image && !imageError) ? category.image : fallbackImage
return (
-
+
setImageError(true)}
+ />
{category.description}
diff --git a/frontend/src/components/ProductCard.jsx b/frontend/src/components/ProductCard.jsx index 6aba573..a9e0ded 100644 --- a/frontend/src/components/ProductCard.jsx +++ b/frontend/src/components/ProductCard.jsx @@ -1,8 +1,9 @@ -import React from 'react' +import React, { useState } from 'react' import { Link } from 'react-router-dom' import '../styles/global.css' export default function ProductCard({ product }) { + const [imageError, setImageError] = useState(false) const price = product.discount_price || product.price const discount = product.discount_price && product.is_on_sale @@ -11,11 +12,20 @@ export default function ProductCard({ product }) { ) : 0 + const fallbackImage = `https://via.placeholder.com/400x400?text=${encodeURIComponent(product.name)}` + const imageUrl = (product.images && product.images.length > 0 && !imageError) + ? product.images[0] + : fallbackImage + return (