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 ( - {category.name} + {category.name} setImageError(true)} + />

{category.name}

{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 (
- {product.name} + {product.name} setImageError(true)} + /> {product.is_on_sale && discount > 0 && (
{discount}% OFF
)} diff --git a/frontend/src/pages/Admin.jsx b/frontend/src/pages/Admin.jsx index 1bfbdf0..cc844ef 100644 --- a/frontend/src/pages/Admin.jsx +++ b/frontend/src/pages/Admin.jsx @@ -167,10 +167,6 @@ export default function Admin() { const newImages = [...uploadedImages] try { - // Get backend URL from environment - const apiUrl = import.meta.env.VITE_API_URL || 'http://localhost:8000/api' - const backendUrl = apiUrl.replace('/api', '') // Remove /api suffix to get base URL - for (let i = 0; i < files.length; i++) { const file = files[i] const formDataUpload = new FormData() @@ -182,11 +178,8 @@ export default function Admin() { }, }) - // Add the full URL - const imageUrl = response.data.url.startsWith('http') - ? response.data.url - : `${backendUrl}${response.data.url}` - newImages.push(imageUrl) + // Backend now returns full URLs + newImages.push(response.data.url) } setUploadedImages(newImages) @@ -341,16 +334,11 @@ export default function Admin() { formData.append('file', file) try { - const response = await api.post('/products/upload-image', formData, { + const response = await api.post('/categories/upload-image', formData, { headers: { 'Content-Type': 'multipart/form-data' } }) - // Prepend backend URL for image display - const apiUrl = import.meta.env.VITE_API_URL || 'http://localhost:8000/api' - const backendUrl = apiUrl.replace('/api', '') // Remove /api suffix to get base URL - const imageUrl = response.data.url.startsWith('http') - ? response.data.url - : `${backendUrl}${response.data.url}` - setCategoryFormData({ ...categoryFormData, image: imageUrl }) + // Backend now returns full URLs + setCategoryFormData({ ...categoryFormData, image: response.data.url }) setToast({ type: 'success', message: 'Image uploaded successfully!' }) } catch (error) { console.error('Error uploading image:', error)