import React, { useState, useEffect, useRef } from "react"; import { useParams, useNavigate } from "react-router-dom"; import { Item, Rental, Address } from "../types"; import { useAuth } from "../contexts/AuthContext"; import { itemAPI, rentalAPI, addressAPI, userAPI } from "../services/api"; import { uploadImagesWithVariants, getImageUrl } from "../services/uploadService"; import AvailabilitySettings from "../components/AvailabilitySettings"; import ImageUpload from "../components/ImageUpload"; import ItemInformation from "../components/ItemInformation"; import LocationForm from "../components/LocationForm"; import PricingForm from "../components/PricingForm"; import RulesForm from "../components/RulesForm"; import { IMAGE_LIMITS } from "../config/imageLimits"; interface ItemFormData { name: string; description: string; pricePerHour?: number | string; pricePerDay?: number | string; pricePerWeek?: number | string; pricePerMonth?: number | string; replacementCost: number | string; address1: string; address2: string; city: string; state: string; zipCode: string; country: string; latitude?: number; longitude?: number; rules?: string; generalAvailableAfter: string; generalAvailableBefore: string; specifyTimesPerDay: boolean; weeklyTimes: { sunday: { availableAfter: string; availableBefore: string }; monday: { availableAfter: string; availableBefore: string }; tuesday: { availableAfter: string; availableBefore: string }; wednesday: { availableAfter: string; availableBefore: string }; thursday: { availableAfter: string; availableBefore: string }; friday: { availableAfter: string; availableBefore: string }; saturday: { availableAfter: string; availableBefore: string }; }; } const EditItem: React.FC = () => { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const { user } = useAuth(); const [loading, setLoading] = useState(true); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(null); const [success, setSuccess] = useState(false); const [imageFiles, setImageFiles] = useState([]); const [imagePreviews, setImagePreviews] = useState([]); const [existingImageKeys, setExistingImageKeys] = useState([]); // S3 keys for existing images const [acceptedRentals, setAcceptedRentals] = useState([]); const [userAddresses, setUserAddresses] = useState([]); const [selectedAddressId, setSelectedAddressId] = useState(""); const [addressesLoading, setAddressesLoading] = useState(true); const [selectedPricingUnit, setSelectedPricingUnit] = useState("day"); const [showAdvancedPricing, setShowAdvancedPricing] = useState(false); const [enabledPricingTiers, setEnabledPricingTiers] = useState({ hour: false, day: false, week: false, month: false, }); // Reference to LocationForm geocoding function const geocodeLocationRef = useRef< (() => Promise<{ latitude: number; longitude: number } | null>) | null >(null); const [formData, setFormData] = useState({ name: "", description: "", pricePerHour: "", pricePerDay: "", replacementCost: "", address1: "", address2: "", city: "", state: "", zipCode: "", country: "US", rules: "", generalAvailableAfter: "00:00", generalAvailableBefore: "23:00", specifyTimesPerDay: false, weeklyTimes: { 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" }, }, }); useEffect(() => { fetchItem(); fetchAcceptedRentals(); fetchUserAddresses(); }, [id]); const fetchUserAddresses = async () => { try { const response = await addressAPI.getAddresses(); setUserAddresses(response.data || []); } catch (error) { console.error("Error fetching addresses:", error); } finally { setAddressesLoading(false); } }; const fetchItem = async () => { try { const response = await itemAPI.getItem(id!); const item: Item = response.data; if (item.ownerId !== user?.id) { setError("You are not authorized to edit this item"); return; } // Convert item data to form data format setFormData({ name: item.name, description: item.description, pricePerHour: item.pricePerHour || "", pricePerDay: item.pricePerDay || "", pricePerWeek: item.pricePerWeek || "", pricePerMonth: item.pricePerMonth || "", replacementCost: item.replacementCost || "", address1: item.address1 || "", address2: item.address2 || "", city: item.city || "", state: item.state || "", zipCode: item.zipCode || "", country: item.country || "US", latitude: item.latitude, longitude: item.longitude, rules: item.rules || "", generalAvailableAfter: item.availableAfter || "00:00", generalAvailableBefore: item.availableBefore || "23:00", specifyTimesPerDay: item.specifyTimesPerDay || false, weeklyTimes: item.weeklyTimes || { 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" }, }, }); // Set existing images - store S3 keys and generate preview URLs if (item.imageFilenames && item.imageFilenames.length > 0) { setExistingImageKeys(item.imageFilenames); // Generate preview URLs from S3 keys (use thumbnail for previews) setImagePreviews(item.imageFilenames.map((key: string) => getImageUrl(key, 'thumbnail'))); } // Determine which pricing unit to select based on existing data // Priority: hour -> day -> week -> month (first one with a value) if (item.pricePerHour) { setSelectedPricingUnit("hour"); } else if (item.pricePerDay) { setSelectedPricingUnit("day"); } else if (item.pricePerWeek) { setSelectedPricingUnit("week"); } else if (item.pricePerMonth) { setSelectedPricingUnit("month"); } else { setSelectedPricingUnit("day"); // Default to day if no pricing is set } // Set enabled tiers based on which prices are populated setEnabledPricingTiers({ hour: !!(item.pricePerHour && Number(item.pricePerHour) > 0), day: !!(item.pricePerDay && Number(item.pricePerDay) > 0), week: !!(item.pricePerWeek && Number(item.pricePerWeek) > 0), month: !!(item.pricePerMonth && Number(item.pricePerMonth) > 0), }); // Auto-expand advanced section if multiple pricing tiers are set const pricingTiersSet = [ item.pricePerHour, item.pricePerDay, item.pricePerWeek, item.pricePerMonth, ].filter((price) => price && Number(price) > 0).length; if (pricingTiersSet > 1) { setShowAdvancedPricing(true); } } catch (err: any) { setError(err.response?.data?.message || "Failed to fetch item"); } finally { setLoading(false); } }; const fetchAcceptedRentals = async () => { try { const response = await rentalAPI.getListings(); const rentals: Rental[] = response.data; // Filter for accepted rentals for this specific item const itemRentals = rentals.filter( (rental) => rental.itemId === id && ["confirmed", "active"].includes(rental.status) ); setAcceptedRentals(itemRentals); } catch (err) { console.error("Error fetching rentals:", err); } }; const handleChange = ( e: React.ChangeEvent< HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement > ) => { const { name, value, type } = e.target; if (type === "checkbox") { const checked = (e.target as HTMLInputElement).checked; setFormData((prev) => ({ ...prev, [name]: checked })); } else if (type === "number") { setFormData((prev) => ({ ...prev, [name]: value === "" ? "" : parseFloat(value) || 0, })); } else { setFormData((prev) => ({ ...prev, [name]: value })); } }; const handleCoordinatesChange = (latitude: number, longitude: number) => { setFormData((prev) => ({ ...prev, latitude, longitude, })); }; 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 let geocodedCoordinates = null; if (geocodeLocationRef.current) { try { geocodedCoordinates = await geocodeLocationRef.current(); } catch (error) { console.warn( "Geocoding failed, updating item without coordinates:", error ); } } else { console.warn("No geocoding function available"); } try { // Upload new images to S3 and get their keys (with resizing) let newImageKeys: string[] = []; if (imageFiles.length > 0) { const uploadResults = await uploadImagesWithVariants("item", imageFiles); newImageKeys = uploadResults.map((result) => result.baseKey); } // Combine existing S3 keys with newly uploaded keys const allImageKeys = [...existingImageKeys, ...newImageKeys]; const updatePayload = { ...formData, // Use geocoded coordinates if available, otherwise fall back to formData latitude: geocodedCoordinates?.latitude ?? formData.latitude, longitude: geocodedCoordinates?.longitude ?? formData.longitude, pricePerDay: formData.pricePerDay ? parseFloat(formData.pricePerDay.toString()) : undefined, pricePerHour: formData.pricePerHour ? parseFloat(formData.pricePerHour.toString()) : undefined, pricePerWeek: formData.pricePerWeek ? parseFloat(formData.pricePerWeek.toString()) : undefined, pricePerMonth: formData.pricePerMonth ? parseFloat(formData.pricePerMonth.toString()) : undefined, replacementCost: formData.replacementCost ? parseFloat(formData.replacementCost.toString()) : 0, availableAfter: formData.generalAvailableAfter, availableBefore: formData.generalAvailableBefore, specifyTimesPerDay: formData.specifyTimesPerDay, weeklyTimes: formData.weeklyTimes, imageFilenames: allImageKeys, }; await itemAPI.updateItem(id!, updatePayload); // Check if user has other items - only save to user profile if no other items try { const userItemsResponse = await itemAPI.getItems({ owner: user?.id }); const userItems = userItemsResponse.data.items || []; const hasOtherItems = userItems.filter((item: Item) => item.id !== id).length > 0; if (!hasOtherItems) { // Save to user profile - this is still their primary/only item await userAPI.updateAvailability({ generalAvailableAfter: formData.generalAvailableAfter, generalAvailableBefore: formData.generalAvailableBefore, specifyTimesPerDay: formData.specifyTimesPerDay, weeklyTimes: formData.weeklyTimes, }); } } catch (availabilityError) { console.error("Failed to save availability:", availabilityError); // Don't fail item update if availability save fails } setSuccess(true); setTimeout(() => { navigate(`/items/${id}`); }, 1500); } catch (err: any) { setError(err.response?.data?.message || err.message || "Failed to update item"); } finally { setSubmitting(false); } }; const handleImageChange = (e: React.ChangeEvent) => { const files = Array.from(e.target.files || []); if (imagePreviews.length + files.length > IMAGE_LIMITS.items) { setError(`You can upload a maximum of ${IMAGE_LIMITS.items} images`); return; } const newImageFiles = [...imageFiles, ...files]; setImageFiles(newImageFiles); setError(null); // Clear any previous error // Create previews files.forEach((file) => { const reader = new FileReader(); reader.onloadend = () => { setImagePreviews((prev) => [...prev, reader.result as string]); }; reader.readAsDataURL(file); }); }; const removeImage = (index: number) => { // Check if removing an existing image or a new upload if (index < existingImageKeys.length) { // Removing an existing S3 image setExistingImageKeys((prev) => prev.filter((_, i) => i !== index)); } else { // Removing a new upload - adjust index for the imageFiles array const newFileIndex = index - existingImageKeys.length; setImageFiles((prev) => prev.filter((_, i) => i !== newFileIndex)); } // Always update previews setImagePreviews((prev) => prev.filter((_, i) => i !== index)); }; const handleAddressSelect = (addressId: string) => { if (addressId === "new") { setFormData((prev) => ({ ...prev, address1: "", address2: "", city: "", state: "", zipCode: "", country: "US", })); setSelectedAddressId(""); } else { const selectedAddress = userAddresses.find( (addr) => addr.id === addressId ); if (selectedAddress) { setFormData((prev) => ({ ...prev, address1: selectedAddress.address1, address2: selectedAddress.address2 || "", city: selectedAddress.city, state: selectedAddress.state, zipCode: selectedAddress.zipCode, country: selectedAddress.country, latitude: selectedAddress.latitude, longitude: selectedAddress.longitude, })); setSelectedAddressId(addressId); } } }; const formatAddressDisplay = (address: Address) => { return `${address.address1}, ${address.city}, ${address.state} ${address.zipCode}`; }; const handleWeeklyTimeChange = ( day: string, field: "availableAfter" | "availableBefore", value: string ) => { setFormData((prev) => ({ ...prev, weeklyTimes: { ...prev.weeklyTimes, [day]: { ...prev.weeklyTimes[day as keyof typeof prev.weeklyTimes], [field]: value, }, }, })); }; const handlePricingUnitChange = (e: React.ChangeEvent) => { setSelectedPricingUnit(e.target.value); }; const handleToggleAdvancedPricing = () => { setShowAdvancedPricing((prev) => !prev); }; const handleTierToggle = (tier: string) => { setEnabledPricingTiers((prev) => ({ ...prev, [tier]: !prev[tier as keyof typeof prev], })); }; if (loading) { return (
Loading...
); } if (error && error.includes("authorized")) { return (
{error}
); } return (

Edit Listing

{error && (
{error}
)} {success && (
Item updated successfully! Redirecting...
)}
{ geocodeLocationRef.current = geocodeFunction; }} />
{ setFormData((prev) => ({ ...prev, [field]: value })); }} onWeeklyTimeChange={handleWeeklyTimeChange} />
); }; export default EditItem;