Files
rentall-app/frontend/src/pages/Profile.tsx
2025-09-09 22:49:55 -04:00

1503 lines
56 KiB
TypeScript

import React, { useState, useEffect, useCallback } from "react";
import { useAuth } from "../contexts/AuthContext";
import { userAPI, itemAPI, rentalAPI, addressAPI } from "../services/api";
import { User, Item, Rental, Address } from "../types";
import { getImageUrl } from "../utils/imageUrl";
import AvailabilitySettings from "../components/AvailabilitySettings";
import ReviewItemModal from "../components/ReviewModal";
import ReviewRenterModal from "../components/ReviewRenterModal";
import ReviewDetailsModal from "../components/ReviewDetailsModal";
import {
geocodingService,
AddressComponents,
} from "../services/geocodingService";
const Profile: React.FC = () => {
const { user, updateUser, logout } = useAuth();
const [loading, setLoading] = useState(true);
const [editing, setEditing] = useState(false);
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState<string | null>(null);
const [activeSection, setActiveSection] = useState<string>("overview");
const [profileData, setProfileData] = useState<User | null>(null);
const [formData, setFormData] = useState({
firstName: "",
lastName: "",
email: "",
phone: "",
address1: "",
address2: "",
city: "",
state: "",
zipCode: "",
country: "",
profileImage: "",
});
const [imageFile, setImageFile] = useState<File | null>(null);
const [imagePreview, setImagePreview] = useState<string | null>(null);
const [stats, setStats] = useState({
itemsListed: 0,
acceptedRentals: 0,
totalRentals: 0,
});
const [availabilityData, setAvailabilityData] = useState({
generalAvailableAfter: "09:00",
generalAvailableBefore: "17: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" },
},
});
const [userAddresses, setUserAddresses] = useState<Address[]>([]);
const [addressesLoading, setAddressesLoading] = useState(true);
const [showAddressForm, setShowAddressForm] = useState(false);
const [editingAddressId, setEditingAddressId] = useState<string | null>(null);
const [addressFormData, setAddressFormData] = useState({
address1: "",
address2: "",
city: "",
state: "",
zipCode: "",
country: "US",
latitude: undefined as number | undefined,
longitude: undefined as number | undefined,
});
const [addressGeocoding, setAddressGeocoding] = useState(false);
const [addressGeocodeError, setAddressGeocodeError] = useState<string | null>(
null
);
const [addressGeocodeSuccess, setAddressGeocodeSuccess] = useState(false);
// Rental history state
const [pastRenterRentals, setPastRenterRentals] = useState<Rental[]>([]);
const [pastOwnerRentals, setPastOwnerRentals] = useState<Rental[]>([]);
const [rentalHistoryLoading, setRentalHistoryLoading] = useState(true);
const [showReviewModal, setShowReviewModal] = useState(false);
const [showReviewRenterModal, setShowReviewRenterModal] = useState(false);
const [selectedRental, setSelectedRental] = useState<Rental | null>(null);
const [selectedRentalForReview, setSelectedRentalForReview] =
useState<Rental | null>(null);
const [showReviewDetailsModal, setShowReviewDetailsModal] = useState(false);
const [selectedRentalForDetails, setSelectedRentalForDetails] =
useState<Rental | null>(null);
const [reviewDetailsUserType, setReviewDetailsUserType] = useState<
"renter" | "owner"
>("renter");
useEffect(() => {
fetchProfile();
fetchStats();
fetchUserAddresses();
fetchUserAvailability();
fetchRentalHistory();
}, []);
const fetchUserAvailability = async () => {
try {
const response = await userAPI.getAvailability();
setAvailabilityData(response.data);
} catch (error) {
console.error("Error fetching user availability:", error);
}
};
const fetchUserAddresses = async () => {
try {
const response = await addressAPI.getAddresses();
setUserAddresses(response.data);
} catch (error) {
console.error("Error fetching addresses:", error);
} finally {
setAddressesLoading(false);
}
};
const fetchProfile = async () => {
try {
const response = await userAPI.getProfile();
setProfileData(response.data);
setFormData({
firstName: response.data.firstName || "",
lastName: response.data.lastName || "",
email: response.data.email || "",
phone: response.data.phone || "",
address1: response.data.address1 || "",
address2: response.data.address2 || "",
city: response.data.city || "",
state: response.data.state || "",
zipCode: response.data.zipCode || "",
country: response.data.country || "",
profileImage: response.data.profileImage || "",
});
if (response.data.profileImage) {
setImagePreview(getImageUrl(response.data.profileImage));
}
} catch (err: any) {
setError(err.response?.data?.message || "Failed to fetch profile");
} finally {
setLoading(false);
}
};
const fetchStats = async () => {
try {
// Fetch user's items
const itemsResponse = await itemAPI.getItems();
const allItems = itemsResponse.data.items || itemsResponse.data || [];
const myItems = allItems.filter(
(item: Item) => item.ownerId === user?.id
);
// Fetch rentals where user is the owner (rentals on user's items)
const ownerRentalsResponse = await rentalAPI.getMyListings();
const ownerRentals: Rental[] = ownerRentalsResponse.data;
const acceptedRentals = ownerRentals.filter((r) =>
["confirmed", "active"].includes(r.status)
);
setStats({
itemsListed: myItems.length,
acceptedRentals: acceptedRentals.length,
totalRentals: ownerRentals.length,
});
} catch (err) {
console.error("Failed to fetch stats:", err);
}
};
// Helper functions for rental history
const formatTime = (timeString?: string) => {
if (!timeString || timeString.trim() === "") return "";
try {
const [hour, minute] = timeString.split(":");
const hourNum = parseInt(hour);
const hour12 = hourNum === 0 ? 12 : hourNum > 12 ? hourNum - 12 : hourNum;
const period = hourNum < 12 ? "AM" : "PM";
return `${hour12}:${minute} ${period}`;
} catch (error) {
return "";
}
};
const formatDateTime = (dateTimeString: string) => {
const date = new Date(dateTimeString).toLocaleDateString();
return date;
};
const fetchRentalHistory = async () => {
try {
// Fetch past rentals as a renter
const renterResponse = await rentalAPI.getMyRentals();
const pastRenterRentals = renterResponse.data.filter((r: Rental) =>
["completed", "cancelled"].includes(r.status)
);
setPastRenterRentals(pastRenterRentals);
// Fetch past rentals as an owner
const ownerResponse = await rentalAPI.getMyListings();
const pastOwnerRentals = ownerResponse.data.filter((r: Rental) =>
["completed", "cancelled"].includes(r.status)
);
setPastOwnerRentals(pastOwnerRentals);
} catch (err) {
console.error("Failed to fetch rental history:", err);
} finally {
setRentalHistoryLoading(false);
}
};
const handleReviewClick = (rental: Rental) => {
setSelectedRental(rental);
setShowReviewModal(true);
};
const handleReviewSuccess = () => {
fetchRentalHistory(); // Refresh to show updated review status
alert("Thank you for your review!");
};
const handleCompleteClick = async (rental: Rental) => {
try {
await rentalAPI.markAsCompleted(rental.id);
setSelectedRentalForReview(rental);
setShowReviewRenterModal(true);
fetchRentalHistory(); // Refresh rental history
} catch (err: any) {
alert(
"Failed to mark rental as completed: " +
(err.response?.data?.error || err.message)
);
}
};
const handleReviewRenterSuccess = () => {
fetchRentalHistory(); // Refresh to show updated review status
};
const handleViewReviewDetails = (
rental: Rental,
userType: "renter" | "owner"
) => {
setSelectedRentalForDetails(rental);
setReviewDetailsUserType(userType);
setShowReviewDetailsModal(true);
};
const handleCloseReviewDetails = () => {
setShowReviewDetailsModal(false);
setSelectedRentalForDetails(null);
};
const handleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
const { name, value } = e.target;
setFormData((prev) => ({ ...prev, [name]: value }));
};
const handleImageChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
setImageFile(file);
// Show preview
const reader = new FileReader();
reader.onloadend = () => {
setImagePreview(reader.result as string);
};
reader.readAsDataURL(file);
// Upload image immediately
try {
const formData = new FormData();
formData.append("profileImage", file);
const response = await userAPI.uploadProfileImage(formData);
// Update the profileImage in formData with the new filename
setFormData((prev) => ({
...prev,
profileImage: response.data.filename,
}));
// Update preview to use the uploaded image URL
setImagePreview(getImageUrl(response.data.imageUrl));
} catch (err: any) {
console.error("Image upload error:", err);
setError(err.response?.data?.error || "Failed to upload image");
// Reset on error
setImageFile(null);
setImagePreview(
profileData?.profileImage
? getImageUrl(profileData.profileImage)
: null
);
}
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
setSuccess(null);
try {
// Don't send profileImage in the update data as it's handled separately
const { profileImage, ...updateData } = formData;
const response = await userAPI.updateProfile(updateData);
setProfileData(response.data);
updateUser(response.data); // Update the auth context
setEditing(false);
} catch (err: any) {
console.error("Profile update error:", err.response?.data);
const errorMessage =
err.response?.data?.error ||
err.response?.data?.message ||
"Failed to update profile";
const errorDetails = err.response?.data?.details;
if (errorDetails && Array.isArray(errorDetails)) {
const detailMessages = errorDetails
.map((d: any) => `${d.field}: ${d.message}`)
.join(", ");
setError(`${errorMessage} - ${detailMessages}`);
} else {
setError(errorMessage);
}
}
};
const handleCancel = () => {
setEditing(false);
setError(null);
setSuccess(null);
// Reset form to original data
if (profileData) {
setFormData({
firstName: profileData.firstName || "",
lastName: profileData.lastName || "",
email: profileData.email || "",
phone: profileData.phone || "",
address1: profileData.address1 || "",
address2: profileData.address2 || "",
city: profileData.city || "",
state: profileData.state || "",
zipCode: profileData.zipCode || "",
country: profileData.country || "",
profileImage: profileData.profileImage || "",
});
setImagePreview(
profileData.profileImage ? getImageUrl(profileData.profileImage) : null
);
}
};
const handleAvailabilityChange = (field: string, value: string | boolean) => {
setAvailabilityData((prev) => ({ ...prev, [field]: value }));
};
const handleWeeklyTimeChange = (
day: string,
field: "availableAfter" | "availableBefore",
value: string
) => {
setAvailabilityData((prev) => ({
...prev,
weeklyTimes: {
...prev.weeklyTimes,
[day]: {
...prev.weeklyTimes[day as keyof typeof prev.weeklyTimes],
[field]: value,
},
},
}));
};
const handleSaveAvailability = async () => {
try {
await userAPI.updateAvailability(availabilityData);
setSuccess("Availability settings saved successfully");
setTimeout(() => setSuccess(null), 3000);
} catch (error) {
console.error("Error saving availability:", error);
setError("Failed to save availability settings");
}
};
const formatAddressDisplay = (address: Address) => {
return `${address.address1}, ${address.city}, ${address.state} ${address.zipCode}`;
};
const handleDeleteAddress = async (addressId: string) => {
try {
await addressAPI.deleteAddress(addressId);
setUserAddresses((prev) => prev.filter((addr) => addr.id !== addressId));
} catch (error) {
console.error("Error deleting address:", error);
setError("Failed to delete address");
}
};
const handleAddAddress = () => {
setAddressFormData({
address1: "",
address2: "",
city: "",
state: "",
zipCode: "",
country: "US",
latitude: undefined,
longitude: undefined,
});
setEditingAddressId(null);
setShowAddressForm(true);
};
const handleEditAddress = (address: Address) => {
setAddressFormData({
address1: address.address1,
address2: address.address2 || "",
city: address.city,
state: address.state,
zipCode: address.zipCode,
country: address.country,
latitude: address.latitude,
longitude: address.longitude,
});
setEditingAddressId(address.id);
setShowAddressForm(true);
};
const handleAddressFormChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
) => {
const { name, value } = e.target;
setAddressFormData((prev) => ({ ...prev, [name]: value }));
};
// Geocoding function for address form
const geocodeAddressForm = useCallback(
async (addressData: typeof addressFormData) => {
if (
!geocodingService.isAddressComplete(addressData as AddressComponents)
) {
return;
}
setAddressGeocoding(true);
setAddressGeocodeError(null);
setAddressGeocodeSuccess(false);
try {
const result = await geocodingService.geocodeAddress(
addressData as AddressComponents
);
if ("error" in result) {
setAddressGeocodeError(result.details || result.error);
} else {
setAddressGeocodeSuccess(true);
setAddressFormData((prev) => ({
...prev,
latitude: result.latitude,
longitude: result.longitude,
}));
// Clear success message after 3 seconds
setTimeout(() => setAddressGeocodeSuccess(false), 3000);
}
} catch (error) {
setAddressGeocodeError("Failed to geocode address");
} finally {
setAddressGeocoding(false);
}
},
[]
);
const handleSaveAddress = async (e: React.FormEvent) => {
e.preventDefault();
// Try to geocode the address before saving
try {
await geocodeAddressForm(addressFormData);
} catch (error) {
// Geocoding failed, but we'll continue with saving
console.warn(
"Geocoding failed, saving address without coordinates:",
error
);
}
try {
if (editingAddressId) {
// Update existing address
const response = await addressAPI.updateAddress(
editingAddressId,
addressFormData
);
setUserAddresses((prev) =>
prev.map((addr) =>
addr.id === editingAddressId ? response.data : addr
)
);
} else {
// Create new address
const response = await addressAPI.createAddress({
...addressFormData,
isPrimary: userAddresses.length === 0,
});
setUserAddresses((prev) => [...prev, response.data]);
}
setShowAddressForm(false);
setEditingAddressId(null);
} catch (error) {
console.error("Error saving address:", error);
setError("Failed to save address");
}
};
const handleCancelAddressForm = () => {
setShowAddressForm(false);
setEditingAddressId(null);
setAddressFormData({
address1: "",
address2: "",
city: "",
state: "",
zipCode: "",
country: "US",
latitude: undefined,
longitude: undefined,
});
setAddressGeocoding(false);
setAddressGeocodeError(null);
setAddressGeocodeSuccess(false);
};
const usStates = [
"Alabama",
"Alaska",
"Arizona",
"Arkansas",
"California",
"Colorado",
"Connecticut",
"Delaware",
"Florida",
"Georgia",
"Hawaii",
"Idaho",
"Illinois",
"Indiana",
"Iowa",
"Kansas",
"Kentucky",
"Louisiana",
"Maine",
"Maryland",
"Massachusetts",
"Michigan",
"Minnesota",
"Mississippi",
"Missouri",
"Montana",
"Nebraska",
"Nevada",
"New Hampshire",
"New Jersey",
"New Mexico",
"New York",
"North Carolina",
"North Dakota",
"Ohio",
"Oklahoma",
"Oregon",
"Pennsylvania",
"Rhode Island",
"South Carolina",
"South Dakota",
"Tennessee",
"Texas",
"Utah",
"Vermont",
"Virginia",
"Washington",
"West Virginia",
"Wisconsin",
"Wyoming",
];
if (loading) {
return (
<div className="container mt-5">
<div className="text-center">
<div className="spinner-border" role="status">
<span className="visually-hidden">Loading...</span>
</div>
</div>
</div>
);
}
return (
<div className="container mt-4">
<h1 className="mb-4">Profile</h1>
{error && (
<div className="alert alert-danger" role="alert">
{error}
</div>
)}
{success && (
<div className="alert alert-success" role="alert">
{success}
</div>
)}
<div className="row">
{/* Left Sidebar Menu */}
<div className="col-md-3">
<div className="card">
<div className="list-group list-group-flush">
<button
className={`list-group-item list-group-item-action ${
activeSection === "overview" ? "active" : ""
}`}
onClick={() => setActiveSection("overview")}
>
<i className="bi bi-person-circle me-2"></i>
Overview
</button>
<button
className={`list-group-item list-group-item-action ${
activeSection === "owner-settings" ? "active" : ""
}`}
onClick={() => setActiveSection("owner-settings")}
>
<i className="bi bi-gear me-2"></i>
Owner Settings
</button>
<button
className={`list-group-item list-group-item-action ${
activeSection === "rental-history" ? "active" : ""
}`}
onClick={() => setActiveSection("rental-history")}
>
<i className="bi bi-clock-history me-2"></i>
Rental History
</button>
<button
className={`list-group-item list-group-item-action ${
activeSection === "personal-info" ? "active" : ""
}`}
onClick={() => setActiveSection("personal-info")}
>
<i className="bi bi-person me-2"></i>
Personal Information
</button>
<button
className={`list-group-item list-group-item-action ${
activeSection === "notifications" ? "active" : ""
}`}
onClick={() => setActiveSection("notifications")}
>
<i className="bi bi-bell me-2"></i>
Notification Settings
</button>
<button
className={`list-group-item list-group-item-action ${
activeSection === "privacy" ? "active" : ""
}`}
onClick={() => setActiveSection("privacy")}
>
<i className="bi bi-shield-lock me-2"></i>
Privacy & Security
</button>
<button
className={`list-group-item list-group-item-action ${
activeSection === "payment" ? "active" : ""
}`}
onClick={() => setActiveSection("payment")}
>
<i className="bi bi-credit-card me-2"></i>
Payment Methods
</button>
<button
className="list-group-item list-group-item-action text-danger"
onClick={logout}
>
<i className="bi bi-box-arrow-right me-2"></i>
Log Out
</button>
</div>
</div>
</div>
{/* Right Content Area */}
<div className="col-md-9">
{/* Overview Section */}
{activeSection === "overview" && (
<div>
<h4 className="mb-4">Overview</h4>
{/* Profile Card */}
<div className="card mb-4">
<div className="card-body">
<form onSubmit={handleSubmit}>
<div className="text-center">
<div className="position-relative d-inline-block mb-3">
{imagePreview ? (
<img
src={imagePreview}
alt="Profile"
className="rounded-circle"
style={{
width: "120px",
height: "120px",
objectFit: "cover",
}}
/>
) : (
<div
className="rounded-circle bg-secondary d-flex align-items-center justify-content-center"
style={{ width: "120px", height: "120px" }}
>
<i
className="bi bi-person-fill text-white"
style={{ fontSize: "2.5rem" }}
></i>
</div>
)}
{editing && (
<label
htmlFor="profileImageOverview"
className="position-absolute bottom-0 end-0 btn btn-sm btn-primary rounded-circle"
style={{
width: "35px",
height: "35px",
padding: "0",
}}
>
<i className="bi bi-camera-fill"></i>
<input
type="file"
id="profileImageOverview"
accept="image/*"
onChange={handleImageChange}
className="d-none"
/>
</label>
)}
</div>
{editing ? (
<div>
<div className="row justify-content-center mb-3">
<div className="col-md-6">
<input
type="text"
className="form-control mb-2"
name="firstName"
value={formData.firstName}
onChange={handleChange}
placeholder="First Name"
required
/>
</div>
<div className="col-md-6">
<input
type="text"
className="form-control mb-2"
name="lastName"
value={formData.lastName}
onChange={handleChange}
placeholder="Last Name"
required
/>
</div>
</div>
<div className="d-flex gap-2 justify-content-center">
<button type="submit" className="btn btn-primary">
Save Changes
</button>
<button
type="button"
className="btn btn-secondary"
onClick={handleCancel}
>
Cancel
</button>
</div>
</div>
) : (
<div>
<h5>
{profileData?.firstName} {profileData?.lastName}
</h5>
<p className="text-muted">@{profileData?.username}</p>
{profileData?.isVerified && (
<span className="badge bg-success mb-3">
<i className="bi bi-check-circle-fill"></i>{" "}
Verified
</span>
)}
<div>
<button
type="button"
className="btn btn-outline-primary"
onClick={() => setEditing(true)}
>
<i className="bi bi-pencil me-2"></i>
Edit Profile
</button>
</div>
</div>
)}
</div>
</form>
</div>
</div>
{/* Stats Card */}
<div className="card">
<div className="card-body">
<h5 className="card-title">Account Statistics</h5>
<div className="row text-center">
<div className="col-md-4">
<div className="p-3">
<h4 className="text-primary mb-1">
{stats.itemsListed}
</h4>
<h6 className="text-muted">Items Listed</h6>
</div>
</div>
<div className="col-md-4">
<div className="p-3">
<h4 className="text-success mb-1">
{stats.acceptedRentals}
</h4>
<h6 className="text-muted">Active Rentals</h6>
</div>
</div>
<div className="col-md-4">
<div className="p-3">
<h4 className="text-info mb-1">{stats.totalRentals}</h4>
<h6 className="text-muted">Total Rentals</h6>
</div>
</div>
</div>
</div>
</div>
</div>
)}
{/* Rental History Section */}
{activeSection === "rental-history" && (
<div>
<h4 className="mb-4">Rental History</h4>
{rentalHistoryLoading ? (
<div className="text-center py-5">
<div className="spinner-border" role="status">
<span className="visually-hidden">Loading...</span>
</div>
</div>
) : (
<>
{/* As Renter Section */}
{pastRenterRentals.length > 0 && (
<div className="mb-5">
<h5 className="mb-3">
<i className="bi bi-person me-2"></i>
As Renter ({pastRenterRentals.length})
</h5>
<div className="row">
{pastRenterRentals.map((rental) => (
<div
key={rental.id}
className="col-md-6 col-lg-4 mb-4"
>
<div className="card h-100">
{rental.item?.images && rental.item.images[0] && (
<img
src={rental.item.images[0]}
className="card-img-top"
alt={rental.item.name}
style={{
height: "150px",
objectFit: "cover",
}}
/>
)}
<div className="card-body">
<h6 className="card-title">
{rental.item
? rental.item.name
: "Item Unavailable"}
</h6>
<div className="mb-2">
<span
className={`badge ${
rental.status === "completed"
? "bg-success"
: "bg-danger"
}`}
>
{rental.status.charAt(0).toUpperCase() +
rental.status.slice(1)}
</span>
</div>
<p className="mb-1 small">
<strong>Period:</strong>
<br />
<strong>Start:</strong>{" "}
{formatDateTime(rental.startDateTime)}
<br />
<strong>End:</strong>{" "}
{formatDateTime(rental.endDateTime)}
</p>
<p className="mb-1 small">
<strong>Total:</strong> ${rental.totalAmount}
</p>
{rental.owner && (
<p className="mb-1 small">
<strong>Owner:</strong>{" "}
{rental.owner.firstName}{" "}
{rental.owner.lastName}
</p>
)}
{rental.status === "cancelled" &&
rental.rejectionReason && (
<div className="alert alert-warning mt-2 mb-1 p-2 small">
<strong>Rejection reason:</strong>{" "}
{rental.rejectionReason}
</div>
)}
<div className="d-flex gap-2 mt-3">
{rental.status === "completed" &&
!rental.itemRating &&
!rental.itemReviewSubmittedAt && (
<button
className="btn btn-sm btn-primary"
onClick={() =>
handleReviewClick(rental)
}
>
Review
</button>
)}
{rental.itemReviewSubmittedAt &&
!rental.itemReviewVisible && (
<div className="text-info small">
<i className="bi bi-clock me-1"></i>
Review Submitted
</div>
)}
{((rental.renterPrivateMessage &&
rental.renterReviewVisible) ||
(rental.itemReviewVisible &&
rental.itemRating)) && (
<button
className="btn btn-sm btn-outline-primary mt-2"
onClick={() =>
handleViewReviewDetails(
rental,
"renter"
)
}
>
View Review Details
</button>
)}
{rental.status === "completed" &&
rental.rating &&
!rental.itemRating && (
<div className="text-success small">
<i className="bi bi-check-circle-fill me-1"></i>
Reviewed ({rental.rating}/5)
</div>
)}
</div>
</div>
</div>
</div>
))}
</div>
</div>
)}
{/* As Owner Section */}
{pastOwnerRentals.length > 0 && (
<div className="mb-5">
<h5 className="mb-3">
<i className="bi bi-house me-2"></i>
As Owner ({pastOwnerRentals.length})
</h5>
<div className="row">
{pastOwnerRentals.map((rental) => (
<div
key={rental.id}
className="col-md-6 col-lg-4 mb-4"
>
<div className="card h-100">
{rental.item?.images && rental.item.images[0] && (
<img
src={rental.item.images[0]}
className="card-img-top"
alt={rental.item.name}
style={{
height: "150px",
objectFit: "cover",
}}
/>
)}
<div className="card-body">
<h6 className="card-title">
{rental.item
? rental.item.name
: "Item Unavailable"}
</h6>
{rental.renter && (
<p className="mb-1 small">
<strong>Renter:</strong>{" "}
{rental.renter.firstName}{" "}
{rental.renter.lastName}
</p>
)}
<div className="mb-2">
<span
className={`badge ${
rental.status === "completed"
? "bg-success"
: "bg-danger"
}`}
>
{rental.status.charAt(0).toUpperCase() +
rental.status.slice(1)}
</span>
</div>
<p className="mb-1 small">
<strong>Period:</strong>
<br />
{formatDateTime(rental.startDateTime)} -{" "}
{formatDateTime(rental.endDateTime)}
</p>
<p className="mb-1 small">
<strong>Total:</strong> ${rental.totalAmount}
</p>
<div className="d-flex gap-2 mt-3">
{rental.status === "completed" &&
!rental.renterRating &&
!rental.renterReviewSubmittedAt && (
<button
className="btn btn-sm btn-primary"
onClick={() => {
setSelectedRentalForReview(rental);
setShowReviewRenterModal(true);
}}
>
Review Renter
</button>
)}
{rental.renterReviewSubmittedAt &&
!rental.renterReviewVisible && (
<div className="text-info small">
<i className="bi bi-clock me-1"></i>
Review Submitted
</div>
)}
{((rental.itemPrivateMessage &&
rental.itemReviewVisible) ||
(rental.renterReviewVisible &&
rental.renterRating)) && (
<button
className="btn btn-sm btn-outline-primary mt-2"
onClick={() =>
handleViewReviewDetails(rental, "owner")
}
>
View Review Details
</button>
)}
</div>
</div>
</div>
</div>
))}
</div>
</div>
)}
{/* Empty State */}
{pastRenterRentals.length === 0 &&
pastOwnerRentals.length === 0 && (
<div className="text-center py-5">
<i
className="bi bi-clock-history text-muted mb-3"
style={{ fontSize: "3rem" }}
></i>
<h5 className="text-muted">No Rental History</h5>
<p className="text-muted">
Your completed rentals and rental requests will appear
here.
</p>
</div>
)}
</>
)}
</div>
)}
{/* Personal Information Section */}
{activeSection === "personal-info" && (
<div>
<h4 className="mb-4">Personal Information</h4>
<div className="card">
<div className="card-body">
<form onSubmit={handleSubmit}>
<div className="mb-3">
<label htmlFor="email" className="form-label">
Email
</label>
<input
type="email"
className="form-control"
id="email"
name="email"
value={formData.email}
onChange={handleChange}
disabled={!editing}
/>
</div>
<div className="mb-3">
<label htmlFor="phone" className="form-label">
Phone Number
</label>
<input
type="tel"
className="form-control"
id="phone"
name="phone"
value={formData.phone}
onChange={handleChange}
placeholder="(123) 456-7890"
disabled={!editing}
/>
</div>
{editing ? (
<div className="d-flex gap-2">
<button type="submit" className="btn btn-primary">
Save Changes
</button>
<button
type="button"
className="btn btn-secondary"
onClick={handleCancel}
>
Cancel
</button>
</div>
) : (
<button
type="button"
className="btn btn-primary"
onClick={() => setEditing(true)}
>
Edit Information
</button>
)}
</form>
</div>
</div>
</div>
)}
{/* Owner Settings Section */}
{activeSection === "owner-settings" && (
<div>
<h4 className="mb-4">Owner Settings</h4>
{/* Addresses Card */}
<div className="card mb-4">
<div className="card-body">
<h5 className="card-title">Saved Addresses</h5>
{addressesLoading ? (
<div className="text-center py-3">
<div
className="spinner-border spinner-border-sm"
role="status"
>
<span className="visually-hidden">
Loading addresses...
</span>
</div>
</div>
) : (
<>
{userAddresses.length === 0 && !showAddressForm ? (
<div className="text-center py-3">
<p className="text-muted">No saved addresses yet</p>
<small className="text-muted">
Add an address or create your first listing to save
one automatically
</small>
</div>
) : (
<>
{userAddresses.length > 0 && !showAddressForm && (
<>
<div className="list-group list-group-flush mb-3">
{userAddresses.map((address) => (
<div
key={address.id}
className="list-group-item d-flex justify-content-between align-items-start"
>
<div className="flex-grow-1">
<div className="fw-medium">
{formatAddressDisplay(address)}
</div>
{address.address2 && (
<small className="text-muted">
{address.address2}
</small>
)}
</div>
<div className="btn-group">
<button
className="btn btn-outline-secondary btn-sm"
onClick={() =>
handleEditAddress(address)
}
>
<i className="bi bi-pencil"></i>
</button>
<button
className="btn btn-outline-danger btn-sm"
onClick={() =>
handleDeleteAddress(address.id)
}
>
<i className="bi bi-trash"></i>
</button>
</div>
</div>
))}
</div>
<button
className="btn btn-outline-primary"
onClick={handleAddAddress}
>
Add New Address
</button>
</>
)}
</>
)}
{/* Show Add New Address button even when no addresses exist */}
{userAddresses.length === 0 && !showAddressForm && (
<div className="text-center">
<button
className="btn btn-outline-primary"
onClick={handleAddAddress}
>
Add New Address
</button>
</div>
)}
{/* Address Form */}
{showAddressForm && (
<form onSubmit={handleSaveAddress}>
<div className="row mb-3">
<div className="col-md-6">
<label
htmlFor="addressFormAddress1"
className="form-label"
>
Address Line 1 *
</label>
<input
type="text"
className="form-control"
id="addressFormAddress1"
name="address1"
value={addressFormData.address1}
onChange={handleAddressFormChange}
placeholder=""
required
/>
</div>
<div className="col-md-6">
<label
htmlFor="addressFormAddress2"
className="form-label"
>
Address Line 2
</label>
<input
type="text"
className="form-control"
id="addressFormAddress2"
name="address2"
value={addressFormData.address2}
onChange={handleAddressFormChange}
placeholder="Apt, Suite, Unit, etc."
/>
</div>
</div>
<div className="row mb-3">
<div className="col-md-6">
<label
htmlFor="addressFormCity"
className="form-label"
>
City *
</label>
<input
type="text"
className="form-control"
id="addressFormCity"
name="city"
value={addressFormData.city}
onChange={handleAddressFormChange}
required
/>
</div>
<div className="col-md-3">
<label
htmlFor="addressFormState"
className="form-label"
>
State *
</label>
<select
className="form-select"
id="addressFormState"
name="state"
value={addressFormData.state}
onChange={handleAddressFormChange}
required
>
<option value="">Select State</option>
{usStates.map((state) => (
<option key={state} value={state}>
{state}
</option>
))}
</select>
</div>
<div className="col-md-3">
<label
htmlFor="addressFormZipCode"
className="form-label"
>
ZIP Code *
</label>
<input
type="text"
className="form-control"
id="addressFormZipCode"
name="zipCode"
value={addressFormData.zipCode}
onChange={handleAddressFormChange}
placeholder="12345"
required
/>
</div>
</div>
<div className="d-flex gap-2">
<button type="submit" className="btn btn-primary">
{editingAddressId
? "Update Address"
: "Save Address"}
</button>
<button
type="button"
className="btn btn-secondary"
onClick={handleCancelAddressForm}
>
Cancel
</button>
</div>
</form>
)}
</>
)}
</div>
</div>
{/* Availability Card */}
<div className="card">
<div className="card-body">
<h5 className="card-title">Availability</h5>
<AvailabilitySettings
data={availabilityData}
onChange={handleAvailabilityChange}
onWeeklyTimeChange={handleWeeklyTimeChange}
/>
<button
className="btn btn-outline-success mt-3"
onClick={handleSaveAvailability}
>
Save Availability
</button>
</div>
</div>
</div>
)}
{/* Placeholder sections for other menu items */}
{activeSection === "notifications" && (
<div>
<h4 className="mb-4">Notification Settings</h4>
<div className="card">
<div className="card-body">
<p className="text-muted">
Notification preferences coming soon...
</p>
</div>
</div>
</div>
)}
{activeSection === "privacy" && (
<div>
<h4 className="mb-4">Privacy & Security</h4>
<div className="card">
<div className="card-body">
<p className="text-muted">
Privacy and security settings coming soon...
</p>
</div>
</div>
</div>
)}
</div>
</div>
{/* Review Modals */}
{selectedRental && (
<ReviewItemModal
show={showReviewModal}
onClose={() => {
setShowReviewModal(false);
setSelectedRental(null);
}}
rental={selectedRental}
onSuccess={handleReviewSuccess}
/>
)}
{selectedRentalForReview && (
<ReviewRenterModal
show={showReviewRenterModal}
onClose={() => {
setShowReviewRenterModal(false);
setSelectedRentalForReview(null);
}}
rental={selectedRentalForReview}
onSuccess={handleReviewRenterSuccess}
/>
)}
{/* Review Details Modal */}
{selectedRentalForDetails && (
<ReviewDetailsModal
show={showReviewDetailsModal}
onClose={handleCloseReviewDetails}
rental={selectedRentalForDetails}
userType={reviewDetailsUserType}
/>
)}
</div>
);
};
export default Profile;