Image is required for creating an item, required fields actually required, Available After and Available Before defaults changed, delete confirmation modal for deleting an item

This commit is contained in:
jackiettran
2025-12-29 19:26:37 -05:00
parent ac1e22f194
commit 7dd3aff0f8
10 changed files with 287 additions and 89 deletions

View File

@@ -67,17 +67,17 @@ const CreateItem: React.FC = () => {
state: "",
zipCode: "",
country: "US",
generalAvailableAfter: "09:00",
generalAvailableBefore: "17:00",
generalAvailableAfter: "00:00",
generalAvailableBefore: "23:00",
specifyTimesPerDay: false,
weeklyTimes: {
sunday: { availableAfter: "09:00", availableBefore: "17:00" },
monday: { availableAfter: "09:00", availableBefore: "17:00" },
tuesday: { availableAfter: "09:00", availableBefore: "17:00" },
wednesday: { availableAfter: "09:00", availableBefore: "17:00" },
thursday: { availableAfter: "09:00", availableBefore: "17:00" },
friday: { availableAfter: "09:00", availableBefore: "17:00" },
saturday: { availableAfter: "09:00", availableBefore: "17:00" },
sunday: { availableAfter: "00:00", availableBefore: "23:00" },
monday: { availableAfter: "00:00", availableBefore: "23:00" },
tuesday: { availableAfter: "00:00", availableBefore: "23:00" },
wednesday: { availableAfter: "00:00", availableBefore: "23:00" },
thursday: { availableAfter: "00:00", availableBefore: "23:00" },
friday: { availableAfter: "00:00", availableBefore: "23:00" },
saturday: { availableAfter: "00:00", availableBefore: "23:00" },
},
});
const [imageFiles, setImageFiles] = useState<File[]>([]);
@@ -163,6 +163,48 @@ const CreateItem: React.FC = () => {
return;
}
if (imageFiles.length === 0) {
setError("At least one image is required to create a listing");
document.getElementById("image-upload-section")?.scrollIntoView({ behavior: "smooth", block: "center" });
return;
}
if (!formData.name.trim()) {
setError("Item name is required");
document.getElementById("name")?.focus();
return;
}
if (!formData.address1.trim()) {
setError("Address is required");
document.getElementById("address1")?.focus();
return;
}
if (!formData.city.trim()) {
setError("City is required");
document.getElementById("city")?.focus();
return;
}
if (!formData.state.trim()) {
setError("State is required");
document.getElementById("state")?.focus();
return;
}
if (!formData.zipCode.trim()) {
setError("ZIP code is required");
document.getElementById("zipCode")?.focus();
return;
}
if (!formData.replacementCost || Number(formData.replacementCost) <= 0) {
setError("Replacement cost is required");
document.getElementById("replacementCost")?.focus();
return;
}
setLoading(true);
setError("");
@@ -393,6 +435,7 @@ const CreateItem: React.FC = () => {
const newImageFiles = [...imageFiles, ...files];
setImageFiles(newImageFiles);
setError(""); // Clear any previous error
// Create previews
files.forEach((file) => {
@@ -456,14 +499,16 @@ const CreateItem: React.FC = () => {
</div>
)}
<form onSubmit={handleSubmit}>
<ImageUpload
<form onSubmit={handleSubmit} noValidate>
<div id="image-upload-section">
<ImageUpload
imageFiles={imageFiles}
imagePreviews={imagePreviews}
onImageChange={handleImageChange}
onRemoveImage={removeImage}
error={error}
/>
</div>
<ItemInformation
name={formData.name}

View File

@@ -90,17 +90,17 @@ const EditItem: React.FC = () => {
zipCode: "",
country: "US",
rules: "",
generalAvailableAfter: "09:00",
generalAvailableBefore: "17:00",
generalAvailableAfter: "00:00",
generalAvailableBefore: "23:00",
specifyTimesPerDay: false,
weeklyTimes: {
sunday: { availableAfter: "09:00", availableBefore: "17:00" },
monday: { availableAfter: "09:00", availableBefore: "17:00" },
tuesday: { availableAfter: "09:00", availableBefore: "17:00" },
wednesday: { availableAfter: "09:00", availableBefore: "17:00" },
thursday: { availableAfter: "09:00", availableBefore: "17:00" },
friday: { availableAfter: "09:00", availableBefore: "17:00" },
saturday: { availableAfter: "09:00", availableBefore: "17:00" },
sunday: { availableAfter: "00:00", availableBefore: "23:00" },
monday: { availableAfter: "00:00", availableBefore: "23:00" },
tuesday: { availableAfter: "00:00", availableBefore: "23:00" },
wednesday: { availableAfter: "00:00", availableBefore: "23:00" },
thursday: { availableAfter: "00:00", availableBefore: "23:00" },
friday: { availableAfter: "00:00", availableBefore: "23:00" },
saturday: { availableAfter: "00:00", availableBefore: "23:00" },
},
});
@@ -151,17 +151,17 @@ const EditItem: React.FC = () => {
latitude: item.latitude,
longitude: item.longitude,
rules: item.rules || "",
generalAvailableAfter: item.availableAfter || "09:00",
generalAvailableBefore: item.availableBefore || "17:00",
generalAvailableAfter: item.availableAfter || "00:00",
generalAvailableBefore: item.availableBefore || "23:00",
specifyTimesPerDay: item.specifyTimesPerDay || false,
weeklyTimes: item.weeklyTimes || {
sunday: { availableAfter: "09:00", availableBefore: "17:00" },
monday: { availableAfter: "09:00", availableBefore: "17:00" },
tuesday: { availableAfter: "09:00", availableBefore: "17:00" },
wednesday: { availableAfter: "09:00", availableBefore: "17:00" },
thursday: { availableAfter: "09:00", availableBefore: "17:00" },
friday: { availableAfter: "09:00", availableBefore: "17:00" },
saturday: { availableAfter: "09:00", availableBefore: "17:00" },
sunday: { availableAfter: "00:00", availableBefore: "23:00" },
monday: { availableAfter: "00:00", availableBefore: "23:00" },
tuesday: { availableAfter: "00:00", availableBefore: "23:00" },
wednesday: { availableAfter: "00:00", availableBefore: "23:00" },
thursday: { availableAfter: "00:00", availableBefore: "23:00" },
friday: { availableAfter: "00:00", availableBefore: "23:00" },
saturday: { availableAfter: "00:00", availableBefore: "23:00" },
},
});
@@ -259,6 +259,51 @@ const EditItem: React.FC = () => {
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
// Check total images (existing + new)
const totalImages = existingImageKeys.length + imageFiles.length;
if (totalImages === 0) {
setError("At least one image is required for a listing");
document.getElementById("image-upload-section")?.scrollIntoView({ behavior: "smooth", block: "center" });
return;
}
if (!formData.name.trim()) {
setError("Item name is required");
document.getElementById("name")?.focus();
return;
}
if (!formData.address1.trim()) {
setError("Address is required");
document.getElementById("address1")?.focus();
return;
}
if (!formData.city.trim()) {
setError("City is required");
document.getElementById("city")?.focus();
return;
}
if (!formData.state.trim()) {
setError("State is required");
document.getElementById("state")?.focus();
return;
}
if (!formData.zipCode.trim()) {
setError("ZIP code is required");
document.getElementById("zipCode")?.focus();
return;
}
if (!formData.replacementCost || Number(formData.replacementCost) <= 0) {
setError("Replacement cost is required");
document.getElementById("replacementCost")?.focus();
return;
}
setSubmitting(true);
// Try to geocode the address before submitting
@@ -358,6 +403,7 @@ const EditItem: React.FC = () => {
const newImageFiles = [...imageFiles, ...files];
setImageFiles(newImageFiles);
setError(null); // Clear any previous error
// Create previews
files.forEach((file) => {
@@ -492,14 +538,16 @@ const EditItem: React.FC = () => {
</div>
)}
<form onSubmit={handleSubmit}>
<ImageUpload
imageFiles={imageFiles}
imagePreviews={imagePreviews}
onImageChange={handleImageChange}
onRemoveImage={removeImage}
error={error || ""}
/>
<form onSubmit={handleSubmit} noValidate>
<div id="image-upload-section">
<ImageUpload
imageFiles={imageFiles}
imagePreviews={imagePreviews}
onImageChange={handleImageChange}
onRemoveImage={removeImage}
error={error || ""}
/>
</div>
<ItemInformation
name={formData.name}

View File

@@ -71,6 +71,8 @@ const Owning: React.FC = () => {
useState<ConditionCheck | null>(null);
const [showReturnStatusModal, setShowReturnStatusModal] = useState(false);
const [rentalForReturn, setRentalForReturn] = useState<Rental | null>(null);
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [itemToDelete, setItemToDelete] = useState<Item | null>(null);
useEffect(() => {
fetchListings();
@@ -107,15 +109,23 @@ const Owning: React.FC = () => {
}
};
const handleDelete = async (itemId: string) => {
if (!window.confirm("Are you sure you want to delete this listing?"))
return;
const handleDeleteClick = (item: Item) => {
setItemToDelete(item);
setShowDeleteModal(true);
};
const handleDeleteConfirm = async () => {
if (!itemToDelete) return;
try {
await api.delete(`/items/${itemId}`);
setListings(listings.filter((item) => item.id !== itemId));
await api.delete(`/items/${itemToDelete.id}`);
setListings(listings.filter((item) => item.id !== itemToDelete.id));
setShowDeleteModal(false);
setItemToDelete(null);
} catch (err: any) {
alert("Failed to delete listing");
setError("Failed to delete listing");
setShowDeleteModal(false);
setItemToDelete(null);
}
};
@@ -713,7 +723,7 @@ const Owning: React.FC = () => {
{item.isAvailable ? "Mark Unavailable" : "Mark Available"}
</button>
<button
onClick={() => handleDelete(item.id)}
onClick={() => handleDeleteClick(item)}
className="btn btn-sm btn-outline-danger"
>
Delete
@@ -803,6 +813,54 @@ const Owning: React.FC = () => {
}}
conditionCheck={selectedConditionCheck}
/>
{/* Delete Confirmation Modal */}
{showDeleteModal && (
<div
className="modal fade show d-block"
tabIndex={-1}
style={{ backgroundColor: "rgba(0,0,0,0.5)" }}
>
<div className="modal-dialog modal-dialog-centered">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title">Delete Listing</h5>
<button
type="button"
className="btn-close"
onClick={() => {
setShowDeleteModal(false);
setItemToDelete(null);
}}
></button>
</div>
<div className="modal-body">
<p>Are you sure you want to delete {itemToDelete?.name}?</p>
<p>This action cannot be undone.</p>
</div>
<div className="modal-footer">
<button
type="button"
className="btn btn-secondary"
onClick={() => {
setShowDeleteModal(false);
setItemToDelete(null);
}}
>
Cancel
</button>
<button
type="button"
className="btn btn-danger"
onClick={handleDeleteConfirm}
>
Delete
</button>
</div>
</div>
</div>
</div>
)}
</div>
);
};