fixing bugs with item notification radius

This commit is contained in:
jackiettran
2025-11-20 15:01:15 -05:00
parent 413ac6b6e2
commit 83872fe039
11 changed files with 842 additions and 680 deletions

View File

@@ -12,6 +12,9 @@ import {
geocodingService,
AddressComponents,
} from "../services/geocodingService";
import AddressAutocomplete from "../components/AddressAutocomplete";
import { PlaceDetails } from "../services/placesService";
import { useAddressAutocomplete, usStates } from "../hooks/useAddressAutocomplete";
const Profile: React.FC = () => {
const { user, updateUser, logout } = useAuth();
@@ -398,6 +401,52 @@ const Profile: React.FC = () => {
}
};
const handleSaveNotificationPreferences = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
setSuccess(null);
try {
const response = await userAPI.updateProfile({
itemRequestNotificationRadius: formData.itemRequestNotificationRadius,
});
setProfileData(response.data);
updateUser(response.data);
setSuccess("Notification preferences saved successfully");
setTimeout(() => setSuccess(null), 3000);
} catch (err: any) {
console.error("Notification preferences update error:", err.response?.data);
const errorMessage =
err.response?.data?.error ||
err.response?.data?.message ||
"Failed to update notification preferences";
setError(errorMessage);
}
};
const handleNotificationRadiusChange = async (
e: React.ChangeEvent<HTMLSelectElement>
) => {
const { value } = e.target;
setFormData((prev) => ({ ...prev, itemRequestNotificationRadius: parseInt(value) }));
setError(null);
try {
const response = await userAPI.updateProfile({
itemRequestNotificationRadius: parseInt(value),
});
setProfileData(response.data);
updateUser(response.data);
} catch (err: any) {
console.error("Notification preferences update error:", err.response?.data);
const errorMessage =
err.response?.data?.error ||
err.response?.data?.message ||
"Failed to update notification radius";
setError(errorMessage);
}
};
const formatAddressDisplay = (address: Address) => {
return `${address.address1}, ${address.city}, ${address.state} ${address.zipCode}`;
};
@@ -455,7 +504,7 @@ const Profile: React.FC = () => {
if (
!geocodingService.isAddressComplete(addressData as AddressComponents)
) {
return;
return null;
}
setAddressGeocoding(true);
@@ -469,6 +518,7 @@ const Profile: React.FC = () => {
if ("error" in result) {
setAddressGeocodeError(result.details || result.error);
return null;
} else {
setAddressGeocodeSuccess(true);
setAddressFormData((prev) => ({
@@ -478,9 +528,11 @@ const Profile: React.FC = () => {
}));
// Clear success message after 3 seconds
setTimeout(() => setAddressGeocodeSuccess(false), 3000);
return { latitude: result.latitude, longitude: result.longitude };
}
} catch (error) {
setAddressGeocodeError("Failed to geocode address");
return null;
} finally {
setAddressGeocoding(false);
}
@@ -488,12 +540,13 @@ const Profile: React.FC = () => {
[]
);
const handleSaveAddress = async (e: React.FormEvent) => {
e.preventDefault();
const handleSaveAddress = async (e?: React.FormEvent | React.MouseEvent) => {
e?.preventDefault();
// Try to geocode the address before saving
let coordinates = null;
try {
await geocodeAddressForm(addressFormData);
coordinates = await geocodeAddressForm(addressFormData);
} catch (error) {
// Geocoding failed, but we'll continue with saving
console.warn(
@@ -502,12 +555,21 @@ const Profile: React.FC = () => {
);
}
// Prepare the data to save, including coordinates if geocoding succeeded
const dataToSave = {
...addressFormData,
...(coordinates && {
latitude: coordinates.latitude,
longitude: coordinates.longitude
})
};
try {
if (editingAddressId) {
// Update existing address
const response = await addressAPI.updateAddress(
editingAddressId,
addressFormData
dataToSave
);
setUserAddresses((prev) =>
prev.map((addr) =>
@@ -517,7 +579,7 @@ const Profile: React.FC = () => {
} else {
// Create new address
const response = await addressAPI.createAddress({
...addressFormData,
...dataToSave,
isPrimary: userAddresses.length === 0,
});
setUserAddresses((prev) => [...prev, response.data]);
@@ -548,58 +610,21 @@ const Profile: React.FC = () => {
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",
];
// Use address autocomplete hook
const { parsePlace } = useAddressAutocomplete();
// Handle place selection from autocomplete
const handlePlaceSelect = useCallback((place: PlaceDetails) => {
const parsedAddress = parsePlace(place);
if (parsedAddress) {
setAddressFormData((prev) => ({
...prev,
...parsedAddress,
}));
setAddressGeocodeSuccess(true);
setTimeout(() => setAddressGeocodeSuccess(false), 3000);
}
}, [parsePlace]);
if (loading) {
return (
@@ -652,6 +677,15 @@ const Profile: React.FC = () => {
<i className="bi bi-gear me-2"></i>
Owner Settings
</button>
<button
className={`list-group-item list-group-item-action ${
activeSection === "notification-preferences" ? "active" : ""
}`}
onClick={() => setActiveSection("notification-preferences")}
>
<i className="bi bi-bell me-2"></i>
Notification Preferences
</button>
<button
className={`list-group-item list-group-item-action ${
activeSection === "rental-history" ? "active" : ""
@@ -682,112 +716,59 @@ const Profile: React.FC = () => {
{/* 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 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>
<h5>
{profileData?.firstName} {profileData?.lastName}
</h5>
<p className="text-muted">@{profileData?.username}</p>
<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
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>
</form>
<div>
<h5>
{profileData?.firstName} {profileData?.lastName}
</h5>
<p className="text-muted">@{profileData?.username}</p>
</div>
</div>
</div>
</div>
@@ -807,6 +788,39 @@ const Profile: React.FC = () => {
</div>
{showPersonalInfo && (
<form onSubmit={handleSubmit}>
<div className="row mb-3">
<div className="col-md-6">
<label htmlFor="firstName" className="form-label">
First Name
</label>
<input
type="text"
className="form-control"
id="firstName"
name="firstName"
value={formData.firstName}
onChange={handleChange}
disabled={!editing}
required
/>
</div>
<div className="col-md-6">
<label htmlFor="lastName" className="form-label">
Last Name
</label>
<input
type="text"
className="form-control"
id="lastName"
name="lastName"
value={formData.lastName}
onChange={handleChange}
disabled={!editing}
required
/>
</div>
</div>
<div className="mb-3">
<label htmlFor="email" className="form-label">
Email
@@ -931,7 +945,7 @@ const Profile: React.FC = () => {
{/* Address Form */}
{showAddressForm && (
<form onSubmit={handleSaveAddress}>
<div>
<div className="row mb-3">
<div className="col-md-6">
<label
@@ -940,15 +954,26 @@ const Profile: React.FC = () => {
>
Address Line 1 *
</label>
<input
type="text"
className="form-control"
<AddressAutocomplete
id="addressFormAddress1"
name="address1"
value={addressFormData.address1}
onChange={handleAddressFormChange}
placeholder=""
onChange={(value) => {
const syntheticEvent = {
target: {
name: "address1",
value,
type: "text",
},
} as React.ChangeEvent<HTMLInputElement>;
handleAddressFormChange(syntheticEvent);
}}
onPlaceSelect={handlePlaceSelect}
placeholder="Start typing an address..."
className="form-control"
required
countryRestriction="us"
types={["address"]}
/>
</div>
<div className="col-md-6">
@@ -1025,6 +1050,11 @@ const Profile: React.FC = () => {
name="zipCode"
value={addressFormData.zipCode}
onChange={handleAddressFormChange}
onKeyDown={(e) => {
if (e.key === "Enter") {
handleSaveAddress(e);
}
}}
placeholder="12345"
required
/>
@@ -1032,7 +1062,11 @@ const Profile: React.FC = () => {
</div>
<div className="d-flex gap-2">
<button type="submit" className="btn btn-primary">
<button
type="button"
className="btn btn-primary"
onClick={handleSaveAddress}
>
{editingAddressId
? "Update Address"
: "Save Address"}
@@ -1045,7 +1079,7 @@ const Profile: React.FC = () => {
Cancel
</button>
</div>
</form>
</div>
)}
</>
)}
@@ -1053,36 +1087,6 @@ const Profile: React.FC = () => {
<hr className="my-4" />
{/* Notification Preferences Section */}
<div className="mb-3">
<label className="form-label">Notification Preferences</label>
<div className="mb-3">
<label htmlFor="itemRequestNotificationRadius" className="form-label">
Item Requests Notification Radius
</label>
<select
className="form-select"
id="itemRequestNotificationRadius"
name="itemRequestNotificationRadius"
value={formData.itemRequestNotificationRadius}
onChange={handleChange}
disabled={!editing}
>
<option value="5">5 miles</option>
<option value="10">10 miles</option>
<option value="25">25 miles</option>
<option value="50">50 miles</option>
<option value="100">100 miles</option>
</select>
<div className="form-text">
You'll receive notifications when someone posts an item request within this distance from your primary address
</div>
</div>
</div>
<hr className="my-4" />
{editing ? (
<div className="d-flex gap-2">
<button type="submit" className="btn btn-primary">
@@ -1447,6 +1451,39 @@ const Profile: React.FC = () => {
</div>
</div>
)}
{/* Notification Preferences Section */}
{activeSection === "notification-preferences" && (
<div>
<h4 className="mb-4">Notification Preferences</h4>
<div className="card">
<div className="card-body">
<div className="mb-3">
<label htmlFor="itemRequestNotificationRadius" className="form-label">
Item Requests Notification Radius
</label>
<select
className="form-select"
id="itemRequestNotificationRadius"
name="itemRequestNotificationRadius"
value={formData.itemRequestNotificationRadius}
onChange={handleNotificationRadiusChange}
>
<option value="5">5 miles</option>
<option value="10">10 miles</option>
<option value="25">25 miles</option>
<option value="50">50 miles</option>
<option value="100">100 miles</option>
</select>
<div className="form-text">
You'll receive notifications when someone posts an item request within this distance from your primary address
</div>
</div>
</div>
</div>
</div>
)}
</div>
</div>