diff --git a/API/src/product/updateProductDetails.php b/API/src/product/updateProductDetails.php index b5622a2a..bc1792c4 100644 --- a/API/src/product/updateProductDetails.php +++ b/API/src/product/updateProductDetails.php @@ -28,20 +28,123 @@ } $styleLocation = $_POST["default_style_location"]; $customDetailsRequired = $_POST["customFieldRequired"]; - $product_id = $_SESSION["product_id"]; + // prefer explicit product_id from POST, fallback to session + $product_id = isset($_POST['product_id']) ? intval($_POST['product_id']) : (isset($_SESSION["product_id"]) ? intval($_SESSION["product_id"]) : 0); + if ($product_id === 0) { + echo json_encode(array('success' => false, 'error' => 'product_id not provided')); + exit; + } $sizeAvailable = $_POST["sizeAvailable"]; - // Attempt to insert new design into table + // fetch existing filenames + $fstmt = $conn->prepare("SELECT filename_front, filename_back FROM products WHERE product_id = ? LIMIT 1"); + $fstmt->bind_param("i", $product_id); + $fstmt->execute(); + $fres = $fstmt->get_result(); + $existingFront = ''; + $existingBack = ''; + if ($fres && $fres->num_rows > 0) { + $frow = $fres->fetch_assoc(); + $existingFront = $frow['filename_front']; + $existingBack = $frow['filename_back']; + } + + $newFront = $existingFront; + $newBack = $existingBack; + + // handle explicit removal flags (delete current file and clear DB filename) + if (isset($_POST['remove_front']) && $_POST['remove_front'] == '1') { + // Prevent deleting the side that is configured as the default style location + if ($styleLocation === 'front') { + echo json_encode(array('success' => false, 'error' => 'Cannot delete front design because default style location is front')); + exit; + } + if ($existingFront && $existingFront !== '') { + $oldPath = UPLOAD_DIR . $existingFront; + if (file_exists($oldPath)) {@unlink($oldPath);} + } + $newFront = ''; + } + + if (isset($_POST['remove_back']) && $_POST['remove_back'] == '1') { + // Prevent deleting the side that is configured as the default style location + if ($styleLocation === 'back') { + echo json_encode(array('success' => false, 'error' => 'Cannot delete back design because default style location is back')); + exit; + } + if ($existingBack && $existingBack !== '') { + $oldPath = UPLOAD_DIR . $existingBack; + if (file_exists($oldPath)) {@unlink($oldPath);} + } + $newBack = ''; + } + + // handle uploaded files if provided + if (isset($_FILES['frontFile']) && $_FILES['frontFile']['error'] === UPLOAD_ERR_OK) { + $file = $_FILES['frontFile']; + $orig = basename($file['name']); + // check DB for existing use of this filename by other products + $check = $conn->prepare("SELECT product_id FROM products WHERE (filename_front = ? OR filename_back = ?) AND product_id != ? LIMIT 1"); + $check->bind_param("ssi", $orig, $orig, $product_id); + $check->execute(); + $cres = $check->get_result(); + if ($cres && $cres->num_rows > 0) { + echo json_encode(array('success' => false, 'error' => 'Filename "' . $orig . '" is already used by another product')); + exit; + } + $target = UPLOAD_DIR . $orig; + // delete the old product file first (per requirement) + if ($existingFront && $existingFront !== '') { + $oldPath = UPLOAD_DIR . $existingFront; + if (file_exists($oldPath)) {@unlink($oldPath);} + } + // if a file already exists at the target filename, remove it so we cleanly replace + if (file_exists($target)) {@unlink($target);} + if (!move_uploaded_file($file['tmp_name'], $target)) { + echo json_encode(array('success' => false, 'error' => 'Cannot upload front file')); + exit; + } + $newFront = $orig; + } + + if (isset($_FILES['backFile']) && $_FILES['backFile']['error'] === UPLOAD_ERR_OK) { + $file = $_FILES['backFile']; + $orig = basename($file['name']); + // check DB for existing use of this filename by other products + $check = $conn->prepare("SELECT product_id FROM products WHERE (filename_front = ? OR filename_back = ?) AND product_id != ? LIMIT 1"); + $check->bind_param("ssi", $orig, $orig, $product_id); + $check->execute(); + $cres = $check->get_result(); + if ($cres && $cres->num_rows > 0) { + echo json_encode(array('success' => false, 'error' => 'Filename "' . $orig . '" is already used by another product')); + exit; + } + $target = UPLOAD_DIR . $orig; + // delete the old product file first (per requirement) + if ($existingBack && $existingBack !== '') { + $oldPath = UPLOAD_DIR . $existingBack; + if (file_exists($oldPath)) {@unlink($oldPath);} + } + // if a file already exists at the target filename, remove it so we cleanly replace + if (file_exists($target)) {@unlink($target);} + if (!move_uploaded_file($file['tmp_name'], $target)) { + echo json_encode(array('success' => false, 'error' => 'Cannot upload back file')); + exit; + } + $newBack = $orig; + } + + // Update product including possible new filenames $query = $conn->prepare("UPDATE products - SET product_name = ?, price = ?, tag_list = ?, tColors = ?, lColors = ?, cColors = ?, hColors = ?, categories = ?, subcategories = ?, default_style = ?, style_size = ?, default_style_location = ?, CustomDetailsRequired = ?, sizesAvailable = ? + SET product_name = ?, price = ?, tag_list = ?, tColors = ?, lColors = ?, cColors = ?, hColors = ?, categories = ?, subcategories = ?, default_style = ?, style_size=?, default_style_location = ?, CustomDetailsRequired = ?, sizesAvailable = ?, filename_front = ?, filename_back = ? WHERE product_id = ?;"); - $query->bind_param("sssssssssssssss", $productName, $price, $tags, $tColors, $lColors, $cColors, $hColors, $categories, $subcategories, $defaultStyle, $styleSize, $styleLocation, $customDetailsRequired, $sizeAvailable, $product_id); + $query->bind_param("ssssssssssssssssi", $productName, $price, $tags, $tColors, $lColors, $cColors, $hColors, $categories, $subcategories, $defaultStyle, $styleSize, $styleLocation, $customDetailsRequired, $sizeAvailable, $newFront, $newBack, $product_id); if (!$query->execute()) { // If insertion fails, return error message - echo json_encode("ERR: Insertion failed to execute" . $query->error); + echo json_encode(array('success' => false, 'error' => 'DB update failed: ' . $query->error)); } else { - echo json_encode(1); + echo json_encode(array('success' => true)); } } diff --git a/react/src/products/EditProducts.jsx b/react/src/products/EditProducts.jsx index bb30f96f..18379062 100644 --- a/react/src/products/EditProducts.jsx +++ b/react/src/products/EditProducts.jsx @@ -16,6 +16,10 @@ function EditProducts() { const [lColors, setLColors] = useState(""); const [cColors, setCColors] = useState(""); const [hColors, setHColors] = useState(""); + const [frontFileName, setFrontFileName] = useState(""); + const [backFileName, setBackFileName] = useState(""); + const [frontFile, setFrontFile] = useState(null); + const [backFile, setBackFile] = useState(null); const [allSubcategories, setAllSubcategories] = useState([]); const [currentSubcategories, setCurrentSubcategories] = useState(""); const [allCategories, setAllCategories] = useState([]); @@ -28,6 +32,9 @@ function EditProducts() { const [customFieldRequired, setCustomFieldRequired] = useState(0); const [sizesAvailable, setSizesAvailable] = useState(1); const [failToUpdate, setFailToUpdate] = useState(false); + const [uploadError, setUploadError] = useState(""); + const [removeFront, setRemoveFront] = useState(false); + const [removeBack, setRemoveBack] = useState(false); // API Calls useEffect(() => { @@ -77,6 +84,8 @@ function EditProducts() { setHColors(data.hColors); setStyle(data.default_style); setLocation(data.default_style_location); + setFrontFileName(data.filename_front || ""); + setBackFileName(data.filename_back || ""); setStyleSize(data.style_size || ""); setProductIsSet(true); setCustomFieldRequired(data.CustomDetailsRequired.toString()); @@ -165,14 +174,19 @@ function EditProducts() { } else if (style === 'hoodie' && hColors.trim() === '') { setFailToUpdate(true); } else { - const formData = new FormData(); + const formData = new FormData(); formData.append('productName', productName); + formData.append('product_id', productId); formData.append('price', price); formData.append('tags', tagList); formData.append('tColors', tColors); formData.append('lColors', lColors); formData.append('cColors', cColors); formData.append('hColors', hColors); + if (frontFile) formData.append('frontFile', frontFile); + if (backFile) formData.append('backFile', backFile); + if (removeFront) formData.append('remove_front', '1'); + if (removeBack) formData.append('remove_back', '1'); // send semicolon-separated id lists for categories and subcategories formData.append('categories', selectedCategoryIds.join(';')); formData.append('subcategories', selectedSubcategoryIds.join(';')); @@ -188,13 +202,20 @@ function EditProducts() { }) .then((response) => response.json()) .then((data) => { - if(data) { - window.location.href="/products"; + if (data && data.success) { + window.location.href = "/products"; + } else { + setUploadError((data && data.error) ? data.error : 'Update failed'); } - }); + }) + .catch((err) => setUploadError('Update failed')); } }; + const uploadImage = async (side) => { + // removed: uploadImage now handled on form submit + } + if (admin) { return (
{uploadError}
+