Skip to content

Commit 24cc053

Browse files
authored
Merge pull request #328 from AMTuttle02/feat327_AllowUploadingFromEditPage
Feat327 allow uploading from edit page
2 parents 8c43e1e + 9c3c371 commit 24cc053

File tree

3 files changed

+176
-11
lines changed

3 files changed

+176
-11
lines changed

API/src/product/updateProductDetails.php

Lines changed: 109 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,123 @@
2323
$defaultStyle = $_POST["default_style"];
2424
$styleLocation = $_POST["default_style_location"];
2525
$customDetailsRequired = $_POST["customFieldRequired"];
26-
$product_id = $_SESSION["product_id"];
26+
// prefer explicit product_id from POST, fallback to session
27+
$product_id = isset($_POST['product_id']) ? intval($_POST['product_id']) : (isset($_SESSION["product_id"]) ? intval($_SESSION["product_id"]) : 0);
28+
if ($product_id === 0) {
29+
echo json_encode(array('success' => false, 'error' => 'product_id not provided'));
30+
exit;
31+
}
2732
$sizeAvailable = $_POST["sizeAvailable"];
2833

29-
// Attempt to insert new design into table
34+
// fetch existing filenames
35+
$fstmt = $conn->prepare("SELECT filename_front, filename_back FROM products WHERE product_id = ? LIMIT 1");
36+
$fstmt->bind_param("i", $product_id);
37+
$fstmt->execute();
38+
$fres = $fstmt->get_result();
39+
$existingFront = '';
40+
$existingBack = '';
41+
if ($fres && $fres->num_rows > 0) {
42+
$frow = $fres->fetch_assoc();
43+
$existingFront = $frow['filename_front'];
44+
$existingBack = $frow['filename_back'];
45+
}
46+
47+
$newFront = $existingFront;
48+
$newBack = $existingBack;
49+
50+
// handle explicit removal flags (delete current file and clear DB filename)
51+
if (isset($_POST['remove_front']) && $_POST['remove_front'] == '1') {
52+
// Prevent deleting the side that is configured as the default style location
53+
if ($styleLocation === 'front') {
54+
echo json_encode(array('success' => false, 'error' => 'Cannot delete front design because default style location is front'));
55+
exit;
56+
}
57+
if ($existingFront && $existingFront !== '') {
58+
$oldPath = UPLOAD_DIR . $existingFront;
59+
if (file_exists($oldPath)) {@unlink($oldPath);}
60+
}
61+
$newFront = '';
62+
}
63+
64+
if (isset($_POST['remove_back']) && $_POST['remove_back'] == '1') {
65+
// Prevent deleting the side that is configured as the default style location
66+
if ($styleLocation === 'back') {
67+
echo json_encode(array('success' => false, 'error' => 'Cannot delete back design because default style location is back'));
68+
exit;
69+
}
70+
if ($existingBack && $existingBack !== '') {
71+
$oldPath = UPLOAD_DIR . $existingBack;
72+
if (file_exists($oldPath)) {@unlink($oldPath);}
73+
}
74+
$newBack = '';
75+
}
76+
77+
// handle uploaded files if provided
78+
if (isset($_FILES['frontFile']) && $_FILES['frontFile']['error'] === UPLOAD_ERR_OK) {
79+
$file = $_FILES['frontFile'];
80+
$orig = basename($file['name']);
81+
// check DB for existing use of this filename by other products
82+
$check = $conn->prepare("SELECT product_id FROM products WHERE (filename_front = ? OR filename_back = ?) AND product_id != ? LIMIT 1");
83+
$check->bind_param("ssi", $orig, $orig, $product_id);
84+
$check->execute();
85+
$cres = $check->get_result();
86+
if ($cres && $cres->num_rows > 0) {
87+
echo json_encode(array('success' => false, 'error' => 'Filename "' . $orig . '" is already used by another product'));
88+
exit;
89+
}
90+
$target = UPLOAD_DIR . $orig;
91+
// delete the old product file first (per requirement)
92+
if ($existingFront && $existingFront !== '') {
93+
$oldPath = UPLOAD_DIR . $existingFront;
94+
if (file_exists($oldPath)) {@unlink($oldPath);}
95+
}
96+
// if a file already exists at the target filename, remove it so we cleanly replace
97+
if (file_exists($target)) {@unlink($target);}
98+
if (!move_uploaded_file($file['tmp_name'], $target)) {
99+
echo json_encode(array('success' => false, 'error' => 'Cannot upload front file'));
100+
exit;
101+
}
102+
$newFront = $orig;
103+
}
104+
105+
if (isset($_FILES['backFile']) && $_FILES['backFile']['error'] === UPLOAD_ERR_OK) {
106+
$file = $_FILES['backFile'];
107+
$orig = basename($file['name']);
108+
// check DB for existing use of this filename by other products
109+
$check = $conn->prepare("SELECT product_id FROM products WHERE (filename_front = ? OR filename_back = ?) AND product_id != ? LIMIT 1");
110+
$check->bind_param("ssi", $orig, $orig, $product_id);
111+
$check->execute();
112+
$cres = $check->get_result();
113+
if ($cres && $cres->num_rows > 0) {
114+
echo json_encode(array('success' => false, 'error' => 'Filename "' . $orig . '" is already used by another product'));
115+
exit;
116+
}
117+
$target = UPLOAD_DIR . $orig;
118+
// delete the old product file first (per requirement)
119+
if ($existingBack && $existingBack !== '') {
120+
$oldPath = UPLOAD_DIR . $existingBack;
121+
if (file_exists($oldPath)) {@unlink($oldPath);}
122+
}
123+
// if a file already exists at the target filename, remove it so we cleanly replace
124+
if (file_exists($target)) {@unlink($target);}
125+
if (!move_uploaded_file($file['tmp_name'], $target)) {
126+
echo json_encode(array('success' => false, 'error' => 'Cannot upload back file'));
127+
exit;
128+
}
129+
$newBack = $orig;
130+
}
131+
132+
// Update product including possible new filenames
30133
$query = $conn->prepare("UPDATE products
31-
SET product_name = ?, price = ?, tag_list = ?, tColors = ?, lColors = ?, cColors = ?, hColors = ?, categories = ?, subcategories = ?, default_style = ?, default_style_location = ?, CustomDetailsRequired = ?, sizesAvailable = ?
134+
SET product_name = ?, price = ?, tag_list = ?, tColors = ?, lColors = ?, cColors = ?, hColors = ?, categories = ?, subcategories = ?, default_style = ?, default_style_location = ?, CustomDetailsRequired = ?, sizesAvailable = ?, filename_front = ?, filename_back = ?
32135
WHERE product_id = ?;");
33-
$query->bind_param("ssssssssssssss", $productName, $price, $tags, $tColors, $lColors, $cColors, $hColors, $categories, $subcategories, $defaultStyle, $styleLocation, $customDetailsRequired, $sizeAvailable, $product_id);
136+
$query->bind_param("sssssssssssssssi", $productName, $price, $tags, $tColors, $lColors, $cColors, $hColors, $categories, $subcategories, $defaultStyle, $styleLocation, $customDetailsRequired, $sizeAvailable, $newFront, $newBack, $product_id);
34137
if (!$query->execute()) {
35138
// If insertion fails, return error message
36-
echo json_encode("ERR: Insertion failed to execute" . $query->error);
139+
echo json_encode(array('success' => false, 'error' => 'DB update failed: ' . $query->error));
37140
}
38141
else {
39-
echo json_encode(1);
142+
echo json_encode(array('success' => true));
40143
}
41144
}
42145

react/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "hometeamcreativity-website",
33
"homepage": "https://hometeamcreativity.com",
4-
"version": "4.0.0",
4+
"version": "4.1.0",
55
"description": "Website for Home Team Creativity",
66
"scripts": {
77
"dev": "vite",

react/src/products/EditProducts.jsx

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ function EditProducts() {
1616
const [lColors, setLColors] = useState("");
1717
const [cColors, setCColors] = useState("");
1818
const [hColors, setHColors] = useState("");
19+
const [frontFileName, setFrontFileName] = useState("");
20+
const [backFileName, setBackFileName] = useState("");
21+
const [frontFile, setFrontFile] = useState(null);
22+
const [backFile, setBackFile] = useState(null);
1923
const [allSubcategories, setAllSubcategories] = useState([]);
2024
const [currentSubcategories, setCurrentSubcategories] = useState("");
2125
const [allCategories, setAllCategories] = useState([]);
@@ -27,6 +31,9 @@ function EditProducts() {
2731
const [customFieldRequired, setCustomFieldRequired] = useState(0);
2832
const [sizesAvailable, setSizesAvailable] = useState(1);
2933
const [failToUpdate, setFailToUpdate] = useState(false);
34+
const [uploadError, setUploadError] = useState("");
35+
const [removeFront, setRemoveFront] = useState(false);
36+
const [removeBack, setRemoveBack] = useState(false);
3037

3138
// API Calls
3239
useEffect(() => {
@@ -76,6 +83,8 @@ function EditProducts() {
7683
setHColors(data.hColors);
7784
setStyle(data.default_style);
7885
setLocation(data.default_style_location);
86+
setFrontFileName(data.filename_front || "");
87+
setBackFileName(data.filename_back || "");
7988
setProductIsSet(true);
8089
setCustomFieldRequired(data.CustomDetailsRequired.toString());
8190
setSizesAvailable(data.sizesAvailable.toString());
@@ -159,14 +168,19 @@ function EditProducts() {
159168
} else if (style === 'hoodie' && hColors.trim() === '') {
160169
setFailToUpdate(true);
161170
} else {
162-
const formData = new FormData();
171+
const formData = new FormData();
163172
formData.append('productName', productName);
173+
formData.append('product_id', productId);
164174
formData.append('price', price);
165175
formData.append('tags', tagList);
166176
formData.append('tColors', tColors);
167177
formData.append('lColors', lColors);
168178
formData.append('cColors', cColors);
169179
formData.append('hColors', hColors);
180+
if (frontFile) formData.append('frontFile', frontFile);
181+
if (backFile) formData.append('backFile', backFile);
182+
if (removeFront) formData.append('remove_front', '1');
183+
if (removeBack) formData.append('remove_back', '1');
170184
// send semicolon-separated id lists for categories and subcategories
171185
formData.append('categories', selectedCategoryIds.join(';'));
172186
formData.append('subcategories', selectedSubcategoryIds.join(';'));
@@ -181,13 +195,20 @@ function EditProducts() {
181195
})
182196
.then((response) => response.json())
183197
.then((data) => {
184-
if(data) {
185-
window.location.href="/products";
198+
if (data && data.success) {
199+
window.location.href = "/products";
200+
} else {
201+
setUploadError((data && data.error) ? data.error : 'Update failed');
186202
}
187-
});
203+
})
204+
.catch((err) => setUploadError('Update failed'));
188205
}
189206
};
190207

208+
const uploadImage = async (side) => {
209+
// removed: uploadImage now handled on form submit
210+
}
211+
191212
if (admin) {
192213
return (
193214
<div className="EditProducts">
@@ -442,6 +463,36 @@ function EditProducts() {
442463
</div>
443464
<br/>
444465
<br/>
466+
<h3><b>Front Design</b> <small>Current: {frontFileName ? frontFileName : 'None'}</small></h3>
467+
<div className="containerRow">
468+
<input type="file" accept="image/*" disabled={removeFront} onChange={(e) => setFrontFile(e.target.files[0])} />
469+
{!removeFront ? (
470+
location === 'front' ? (
471+
<button type="button" className="delete-button" disabled title="Cannot delete the default-style image">Delete Front</button>
472+
) : (
473+
<button type="button" className="delete-button" onClick={() => setRemoveFront(true)}>Delete Front</button>
474+
)
475+
) : (
476+
<button type="button" className="default-button" onClick={() => setRemoveFront(false)}>Undo Delete</button>
477+
)}
478+
</div>
479+
<br/>
480+
<br/>
481+
<h3><b>Back Design</b> <small>Current: {backFileName ? backFileName : 'None'}</small></h3>
482+
<div className="containerRow">
483+
<input type="file" accept="image/*" disabled={removeBack} onChange={(e) => setBackFile(e.target.files[0])} />
484+
{!removeBack ? (
485+
location === 'back' ? (
486+
<button type="button" className="delete-button" disabled title="Cannot delete the default-style image">Delete Back</button>
487+
) : (
488+
<button type="button" className="delete-button" onClick={() => setRemoveBack(true)}>Delete Back</button>
489+
)
490+
) : (
491+
<button type="button" className="default-button" onClick={() => setRemoveBack(false)}>Undo Delete</button>
492+
)}
493+
</div>
494+
<br/>
495+
<br/>
445496
<button type="submit" className="default-button">Update Product</button>
446497
</form>
447498
{failToUpdate &&
@@ -455,6 +506,17 @@ function EditProducts() {
455506
</div>
456507
</div>
457508
}
509+
{uploadError &&
510+
<div className="confirmation-modal">
511+
<div className="confirmation-dialog">
512+
<h3>Upload Error</h3>
513+
<p>{uploadError}</p>
514+
<div className="confirmation-buttons">
515+
<button className="delete-button" onClick={() => setUploadError("")}>Close</button>
516+
</div>
517+
</div>
518+
</div>
519+
}
458520
</div>
459521
</div>
460522
</div>

0 commit comments

Comments
 (0)