disable item request notifications
This commit is contained in:
@@ -395,7 +395,21 @@ router.post('/posts', authenticateToken, uploadForumPostImages, async (req, res)
|
|||||||
attributes: ['itemRequestNotificationRadius']
|
attributes: ['itemRequestNotificationRadius']
|
||||||
});
|
});
|
||||||
|
|
||||||
const userPreferredRadius = userProfile?.itemRequestNotificationRadius || 10;
|
const userPreferredRadius = userProfile?.itemRequestNotificationRadius;
|
||||||
|
|
||||||
|
// Skip if user has disabled notifications (null)
|
||||||
|
if (userPreferredRadius === null || userPreferredRadius === undefined) {
|
||||||
|
logger.info("User has disabled item request notifications", {
|
||||||
|
postId: post.id,
|
||||||
|
userId: user.id,
|
||||||
|
userDistance: user.distance
|
||||||
|
});
|
||||||
|
usersSkipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default to 10 miles if somehow not set
|
||||||
|
const effectiveRadius = userPreferredRadius || 10;
|
||||||
|
|
||||||
logger.info("Checking user notification eligibility", {
|
logger.info("Checking user notification eligibility", {
|
||||||
postId: post.id,
|
postId: post.id,
|
||||||
@@ -404,12 +418,12 @@ router.post('/posts', authenticateToken, uploadForumPostImages, async (req, res)
|
|||||||
userCoordinates: { lat: user.latitude, lng: user.longitude },
|
userCoordinates: { lat: user.latitude, lng: user.longitude },
|
||||||
postCoordinates: { lat: latitude, lng: longitude },
|
postCoordinates: { lat: latitude, lng: longitude },
|
||||||
userDistance: user.distance,
|
userDistance: user.distance,
|
||||||
userPreferredRadius,
|
userPreferredRadius: effectiveRadius,
|
||||||
willNotify: parseFloat(user.distance) <= userPreferredRadius
|
willNotify: parseFloat(user.distance) <= effectiveRadius
|
||||||
});
|
});
|
||||||
|
|
||||||
// Only notify if within user's preferred radius
|
// Only notify if within user's preferred radius
|
||||||
if (parseFloat(user.distance) <= userPreferredRadius) {
|
if (parseFloat(user.distance) <= effectiveRadius) {
|
||||||
try {
|
try {
|
||||||
await emailServices.forum.sendItemRequestNotification(
|
await emailServices.forum.sendItemRequestNotification(
|
||||||
user,
|
user,
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ import {
|
|||||||
} from "../services/geocodingService";
|
} from "../services/geocodingService";
|
||||||
import AddressAutocomplete from "../components/AddressAutocomplete";
|
import AddressAutocomplete from "../components/AddressAutocomplete";
|
||||||
import { PlaceDetails } from "../services/placesService";
|
import { PlaceDetails } from "../services/placesService";
|
||||||
import { useAddressAutocomplete, usStates } from "../hooks/useAddressAutocomplete";
|
import {
|
||||||
|
useAddressAutocomplete,
|
||||||
|
usStates,
|
||||||
|
} from "../hooks/useAddressAutocomplete";
|
||||||
|
|
||||||
const Profile: React.FC = () => {
|
const Profile: React.FC = () => {
|
||||||
const { user, updateUser, logout } = useAuth();
|
const { user, updateUser, logout } = useAuth();
|
||||||
@@ -25,7 +28,20 @@ const Profile: React.FC = () => {
|
|||||||
const [success, setSuccess] = useState<string | null>(null);
|
const [success, setSuccess] = useState<string | null>(null);
|
||||||
const [activeSection, setActiveSection] = useState<string>("overview");
|
const [activeSection, setActiveSection] = useState<string>("overview");
|
||||||
const [profileData, setProfileData] = useState<User | null>(null);
|
const [profileData, setProfileData] = useState<User | null>(null);
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState<{
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
email: string;
|
||||||
|
phone: string;
|
||||||
|
address1: string;
|
||||||
|
address2: string;
|
||||||
|
city: string;
|
||||||
|
state: string;
|
||||||
|
zipCode: string;
|
||||||
|
country: string;
|
||||||
|
profileImage: string;
|
||||||
|
itemRequestNotificationRadius: number | null;
|
||||||
|
}>({
|
||||||
firstName: "",
|
firstName: "",
|
||||||
lastName: "",
|
lastName: "",
|
||||||
email: "",
|
email: "",
|
||||||
@@ -141,7 +157,8 @@ const Profile: React.FC = () => {
|
|||||||
zipCode: response.data.zipCode || "",
|
zipCode: response.data.zipCode || "",
|
||||||
country: response.data.country || "",
|
country: response.data.country || "",
|
||||||
profileImage: response.data.profileImage || "",
|
profileImage: response.data.profileImage || "",
|
||||||
itemRequestNotificationRadius: response.data.itemRequestNotificationRadius || 10,
|
itemRequestNotificationRadius:
|
||||||
|
response.data.itemRequestNotificationRadius || 10,
|
||||||
});
|
});
|
||||||
if (response.data.profileImage) {
|
if (response.data.profileImage) {
|
||||||
setImagePreview(getImageUrl(response.data.profileImage));
|
setImagePreview(getImageUrl(response.data.profileImage));
|
||||||
@@ -264,7 +281,9 @@ const Profile: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleChange = (
|
const handleChange = (
|
||||||
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>
|
e: React.ChangeEvent<
|
||||||
|
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
|
||||||
|
>
|
||||||
) => {
|
) => {
|
||||||
const { name, value } = e.target;
|
const { name, value } = e.target;
|
||||||
setFormData((prev) => ({ ...prev, [name]: value }));
|
setFormData((prev) => ({ ...prev, [name]: value }));
|
||||||
@@ -361,7 +380,8 @@ const Profile: React.FC = () => {
|
|||||||
zipCode: profileData.zipCode || "",
|
zipCode: profileData.zipCode || "",
|
||||||
country: profileData.country || "",
|
country: profileData.country || "",
|
||||||
profileImage: profileData.profileImage || "",
|
profileImage: profileData.profileImage || "",
|
||||||
itemRequestNotificationRadius: profileData.itemRequestNotificationRadius || 10,
|
itemRequestNotificationRadius:
|
||||||
|
profileData.itemRequestNotificationRadius || 10,
|
||||||
});
|
});
|
||||||
setImagePreview(
|
setImagePreview(
|
||||||
profileData.profileImage ? getImageUrl(profileData.profileImage) : null
|
profileData.profileImage ? getImageUrl(profileData.profileImage) : null
|
||||||
@@ -415,7 +435,10 @@ const Profile: React.FC = () => {
|
|||||||
setSuccess("Notification preferences saved successfully");
|
setSuccess("Notification preferences saved successfully");
|
||||||
setTimeout(() => setSuccess(null), 3000);
|
setTimeout(() => setSuccess(null), 3000);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error("Notification preferences update error:", err.response?.data);
|
console.error(
|
||||||
|
"Notification preferences update error:",
|
||||||
|
err.response?.data
|
||||||
|
);
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
err.response?.data?.error ||
|
err.response?.data?.error ||
|
||||||
err.response?.data?.message ||
|
err.response?.data?.message ||
|
||||||
@@ -428,7 +451,10 @@ const Profile: React.FC = () => {
|
|||||||
e: React.ChangeEvent<HTMLSelectElement>
|
e: React.ChangeEvent<HTMLSelectElement>
|
||||||
) => {
|
) => {
|
||||||
const { value } = e.target;
|
const { value } = e.target;
|
||||||
setFormData((prev) => ({ ...prev, itemRequestNotificationRadius: parseInt(value) }));
|
setFormData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
itemRequestNotificationRadius: parseInt(value),
|
||||||
|
}));
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -438,7 +464,10 @@ const Profile: React.FC = () => {
|
|||||||
setProfileData(response.data);
|
setProfileData(response.data);
|
||||||
updateUser(response.data);
|
updateUser(response.data);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error("Notification preferences update error:", err.response?.data);
|
console.error(
|
||||||
|
"Notification preferences update error:",
|
||||||
|
err.response?.data
|
||||||
|
);
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
err.response?.data?.error ||
|
err.response?.data?.error ||
|
||||||
err.response?.data?.message ||
|
err.response?.data?.message ||
|
||||||
@@ -560,8 +589,8 @@ const Profile: React.FC = () => {
|
|||||||
...addressFormData,
|
...addressFormData,
|
||||||
...(coordinates && {
|
...(coordinates && {
|
||||||
latitude: coordinates.latitude,
|
latitude: coordinates.latitude,
|
||||||
longitude: coordinates.longitude
|
longitude: coordinates.longitude,
|
||||||
})
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -614,17 +643,20 @@ const Profile: React.FC = () => {
|
|||||||
const { parsePlace } = useAddressAutocomplete();
|
const { parsePlace } = useAddressAutocomplete();
|
||||||
|
|
||||||
// Handle place selection from autocomplete
|
// Handle place selection from autocomplete
|
||||||
const handlePlaceSelect = useCallback((place: PlaceDetails) => {
|
const handlePlaceSelect = useCallback(
|
||||||
const parsedAddress = parsePlace(place);
|
(place: PlaceDetails) => {
|
||||||
if (parsedAddress) {
|
const parsedAddress = parsePlace(place);
|
||||||
setAddressFormData((prev) => ({
|
if (parsedAddress) {
|
||||||
...prev,
|
setAddressFormData((prev) => ({
|
||||||
...parsedAddress,
|
...prev,
|
||||||
}));
|
...parsedAddress,
|
||||||
setAddressGeocodeSuccess(true);
|
}));
|
||||||
setTimeout(() => setAddressGeocodeSuccess(false), 3000);
|
setAddressGeocodeSuccess(true);
|
||||||
}
|
setTimeout(() => setAddressGeocodeSuccess(false), 3000);
|
||||||
}, [parsePlace]);
|
}
|
||||||
|
},
|
||||||
|
[parsePlace]
|
||||||
|
);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
@@ -781,335 +813,344 @@ const Profile: React.FC = () => {
|
|||||||
type="button"
|
type="button"
|
||||||
className="btn btn-link text-primary p-0 ms-2"
|
className="btn btn-link text-primary p-0 ms-2"
|
||||||
onClick={() => setShowPersonalInfo(!showPersonalInfo)}
|
onClick={() => setShowPersonalInfo(!showPersonalInfo)}
|
||||||
style={{ textDecoration: 'none' }}
|
style={{ textDecoration: "none" }}
|
||||||
>
|
>
|
||||||
<i className={`bi ${showPersonalInfo ? 'bi-eye' : 'bi-eye-slash'} fs-5`}></i>
|
<i
|
||||||
|
className={`bi ${
|
||||||
|
showPersonalInfo ? "bi-eye" : "bi-eye-slash"
|
||||||
|
} fs-5`}
|
||||||
|
></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{showPersonalInfo && (
|
{showPersonalInfo && (
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<div className="row mb-3">
|
<div className="row mb-3">
|
||||||
<div className="col-md-6">
|
<div className="col-md-6">
|
||||||
<label htmlFor="firstName" className="form-label">
|
<label htmlFor="firstName" className="form-label">
|
||||||
First Name
|
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
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="email"
|
||||||
className="form-control"
|
className="form-control"
|
||||||
id="firstName"
|
id="email"
|
||||||
name="firstName"
|
name="email"
|
||||||
value={formData.firstName}
|
value={formData.email}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
disabled={!editing}
|
disabled={!editing}
|
||||||
required
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-6">
|
|
||||||
<label htmlFor="lastName" className="form-label">
|
<div className="mb-3">
|
||||||
Last Name
|
<label htmlFor="phone" className="form-label">
|
||||||
|
Phone Number
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="tel"
|
||||||
className="form-control"
|
className="form-control"
|
||||||
id="lastName"
|
id="phone"
|
||||||
name="lastName"
|
name="phone"
|
||||||
value={formData.lastName}
|
value={formData.phone}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
placeholder="(123) 456-7890"
|
||||||
disabled={!editing}
|
disabled={!editing}
|
||||||
required
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mb-3">
|
<hr className="my-4" />
|
||||||
<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">
|
{/* Saved Addresses Section */}
|
||||||
<label htmlFor="phone" className="form-label">
|
<div className="mb-3">
|
||||||
Phone Number
|
<label className="form-label">Saved Addresses</label>
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="tel"
|
|
||||||
className="form-control"
|
|
||||||
id="phone"
|
|
||||||
name="phone"
|
|
||||||
value={formData.phone}
|
|
||||||
onChange={handleChange}
|
|
||||||
placeholder="(123) 456-7890"
|
|
||||||
disabled={!editing}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr className="my-4" />
|
{addressesLoading ? (
|
||||||
|
<div className="text-center py-3">
|
||||||
{/* Saved Addresses Section */}
|
<div
|
||||||
<div className="mb-3">
|
className="spinner-border spinner-border-sm"
|
||||||
<label className="form-label">Saved Addresses</label>
|
role="status"
|
||||||
|
>
|
||||||
{addressesLoading ? (
|
<span className="visually-hidden">
|
||||||
<div className="text-center py-3">
|
Loading addresses...
|
||||||
<div
|
</span>
|
||||||
className="spinner-border spinner-border-sm"
|
</div>
|
||||||
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 mb-2">
|
||||||
|
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 && (
|
||||||
|
<div>
|
||||||
|
<div className="row mb-3">
|
||||||
|
<div className="col-md-6">
|
||||||
|
<label
|
||||||
|
htmlFor="addressFormAddress1"
|
||||||
|
className="form-label"
|
||||||
|
>
|
||||||
|
Address Line 1 *
|
||||||
|
</label>
|
||||||
|
<AddressAutocomplete
|
||||||
|
id="addressFormAddress1"
|
||||||
|
name="address1"
|
||||||
|
value={addressFormData.address1}
|
||||||
|
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">
|
||||||
|
<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}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
handleSaveAddress(e);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
placeholder="12345"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="d-flex gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-primary"
|
||||||
|
onClick={handleSaveAddress}
|
||||||
|
>
|
||||||
|
{editingAddressId
|
||||||
|
? "Update Address"
|
||||||
|
: "Save Address"}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-secondary"
|
||||||
|
onClick={handleCancelAddressForm}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr className="my-4" />
|
||||||
|
|
||||||
|
{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>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
|
||||||
{userAddresses.length === 0 && !showAddressForm ? (
|
|
||||||
<div className="text-center py-3">
|
|
||||||
<p className="text-muted mb-2">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 && (
|
|
||||||
<div>
|
|
||||||
<div className="row mb-3">
|
|
||||||
<div className="col-md-6">
|
|
||||||
<label
|
|
||||||
htmlFor="addressFormAddress1"
|
|
||||||
className="form-label"
|
|
||||||
>
|
|
||||||
Address Line 1 *
|
|
||||||
</label>
|
|
||||||
<AddressAutocomplete
|
|
||||||
id="addressFormAddress1"
|
|
||||||
name="address1"
|
|
||||||
value={addressFormData.address1}
|
|
||||||
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">
|
|
||||||
<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}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === "Enter") {
|
|
||||||
handleSaveAddress(e);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
placeholder="12345"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="d-flex gap-2">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn btn-primary"
|
|
||||||
onClick={handleSaveAddress}
|
|
||||||
>
|
|
||||||
{editingAddressId
|
|
||||||
? "Update Address"
|
|
||||||
: "Save Address"}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn btn-secondary"
|
|
||||||
onClick={handleCancelAddressForm}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr className="my-4" />
|
|
||||||
|
|
||||||
{editing ? (
|
|
||||||
<div className="d-flex gap-2">
|
|
||||||
<button type="submit" className="btn btn-primary">
|
|
||||||
Save Changes
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-secondary"
|
className="btn btn-primary"
|
||||||
onClick={handleCancel}
|
onClick={() => setEditing(true)}
|
||||||
>
|
>
|
||||||
Cancel
|
Edit Information
|
||||||
</button>
|
</button>
|
||||||
</div>
|
)}
|
||||||
) : (
|
</form>
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn btn-primary"
|
|
||||||
onClick={() => setEditing(true)}
|
|
||||||
>
|
|
||||||
Edit Information
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</form>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1225,10 +1266,13 @@ const Profile: React.FC = () => {
|
|||||||
<p className="mb-1 small">
|
<p className="mb-1 small">
|
||||||
<strong>Owner:</strong>{" "}
|
<strong>Owner:</strong>{" "}
|
||||||
<span
|
<span
|
||||||
onClick={() => navigate(`/users/${rental.ownerId}`)}
|
onClick={() =>
|
||||||
|
navigate(`/users/${rental.ownerId}`)
|
||||||
|
}
|
||||||
style={{ cursor: "pointer" }}
|
style={{ cursor: "pointer" }}
|
||||||
>
|
>
|
||||||
{rental.owner.firstName} {rental.owner.lastName}
|
{rental.owner.firstName}{" "}
|
||||||
|
{rental.owner.lastName}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -1330,10 +1374,13 @@ const Profile: React.FC = () => {
|
|||||||
<p className="mb-1 small">
|
<p className="mb-1 small">
|
||||||
<strong>Renter:</strong>{" "}
|
<strong>Renter:</strong>{" "}
|
||||||
<span
|
<span
|
||||||
onClick={() => navigate(`/users/${rental.renterId}`)}
|
onClick={() =>
|
||||||
|
navigate(`/users/${rental.renterId}`)
|
||||||
|
}
|
||||||
style={{ cursor: "pointer" }}
|
style={{ cursor: "pointer" }}
|
||||||
>
|
>
|
||||||
{rental.renter.firstName} {rental.renter.lastName}
|
{rental.renter.firstName}{" "}
|
||||||
|
{rental.renter.lastName}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -1460,26 +1507,84 @@ const Profile: React.FC = () => {
|
|||||||
<div className="card">
|
<div className="card">
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<label htmlFor="itemRequestNotificationRadius" className="form-label">
|
<div className="form-check">
|
||||||
Item Requests Notification Radius
|
<input
|
||||||
</label>
|
className="form-check-input"
|
||||||
<select
|
type="checkbox"
|
||||||
className="form-select"
|
id="enableItemRequestNotifications"
|
||||||
id="itemRequestNotificationRadius"
|
checked={
|
||||||
name="itemRequestNotificationRadius"
|
formData.itemRequestNotificationRadius !== null &&
|
||||||
value={formData.itemRequestNotificationRadius}
|
formData.itemRequestNotificationRadius !== undefined
|
||||||
onChange={handleNotificationRadiusChange}
|
}
|
||||||
>
|
onChange={async (e) => {
|
||||||
<option value="5">5 miles</option>
|
const isEnabled = e.target.checked;
|
||||||
<option value="10">10 miles</option>
|
const newRadius = isEnabled ? 10 : null; // Default to 10 miles when enabled
|
||||||
<option value="25">25 miles</option>
|
setFormData((prev) => ({
|
||||||
<option value="50">50 miles</option>
|
...prev,
|
||||||
<option value="100">100 miles</option>
|
itemRequestNotificationRadius: newRadius,
|
||||||
</select>
|
}));
|
||||||
<div className="form-text">
|
setError(null);
|
||||||
You'll receive notifications when someone posts an item request within this distance from your primary address
|
|
||||||
|
try {
|
||||||
|
const response = await userAPI.updateProfile({
|
||||||
|
itemRequestNotificationRadius: newRadius,
|
||||||
|
});
|
||||||
|
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 preferences";
|
||||||
|
setError(errorMessage);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
className="form-check-label"
|
||||||
|
htmlFor="enableItemRequestNotifications"
|
||||||
|
>
|
||||||
|
Enable Item Request Notifications
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="form-text mb-3">
|
||||||
|
Receive notifications when someone nearby posts an item
|
||||||
|
request
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{formData.itemRequestNotificationRadius !== null &&
|
||||||
|
formData.itemRequestNotificationRadius !== undefined && (
|
||||||
|
<div className="mb-3">
|
||||||
|
<label
|
||||||
|
htmlFor="itemRequestNotificationRadius"
|
||||||
|
className="form-label"
|
||||||
|
>
|
||||||
|
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 for item requests within
|
||||||
|
this distance from your primary address
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user