disable item request notifications
This commit is contained in:
@@ -395,7 +395,21 @@ router.post('/posts', authenticateToken, uploadForumPostImages, async (req, res)
|
||||
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", {
|
||||
postId: post.id,
|
||||
@@ -404,12 +418,12 @@ router.post('/posts', authenticateToken, uploadForumPostImages, async (req, res)
|
||||
userCoordinates: { lat: user.latitude, lng: user.longitude },
|
||||
postCoordinates: { lat: latitude, lng: longitude },
|
||||
userDistance: user.distance,
|
||||
userPreferredRadius,
|
||||
willNotify: parseFloat(user.distance) <= userPreferredRadius
|
||||
userPreferredRadius: effectiveRadius,
|
||||
willNotify: parseFloat(user.distance) <= effectiveRadius
|
||||
});
|
||||
|
||||
// Only notify if within user's preferred radius
|
||||
if (parseFloat(user.distance) <= userPreferredRadius) {
|
||||
if (parseFloat(user.distance) <= effectiveRadius) {
|
||||
try {
|
||||
await emailServices.forum.sendItemRequestNotification(
|
||||
user,
|
||||
|
||||
@@ -14,7 +14,10 @@ import {
|
||||
} from "../services/geocodingService";
|
||||
import AddressAutocomplete from "../components/AddressAutocomplete";
|
||||
import { PlaceDetails } from "../services/placesService";
|
||||
import { useAddressAutocomplete, usStates } from "../hooks/useAddressAutocomplete";
|
||||
import {
|
||||
useAddressAutocomplete,
|
||||
usStates,
|
||||
} from "../hooks/useAddressAutocomplete";
|
||||
|
||||
const Profile: React.FC = () => {
|
||||
const { user, updateUser, logout } = useAuth();
|
||||
@@ -25,7 +28,20 @@ const Profile: React.FC = () => {
|
||||
const [success, setSuccess] = useState<string | null>(null);
|
||||
const [activeSection, setActiveSection] = useState<string>("overview");
|
||||
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: "",
|
||||
lastName: "",
|
||||
email: "",
|
||||
@@ -141,7 +157,8 @@ const Profile: React.FC = () => {
|
||||
zipCode: response.data.zipCode || "",
|
||||
country: response.data.country || "",
|
||||
profileImage: response.data.profileImage || "",
|
||||
itemRequestNotificationRadius: response.data.itemRequestNotificationRadius || 10,
|
||||
itemRequestNotificationRadius:
|
||||
response.data.itemRequestNotificationRadius || 10,
|
||||
});
|
||||
if (response.data.profileImage) {
|
||||
setImagePreview(getImageUrl(response.data.profileImage));
|
||||
@@ -264,7 +281,9 @@ const Profile: React.FC = () => {
|
||||
};
|
||||
|
||||
const handleChange = (
|
||||
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>
|
||||
e: React.ChangeEvent<
|
||||
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
|
||||
>
|
||||
) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData((prev) => ({ ...prev, [name]: value }));
|
||||
@@ -361,7 +380,8 @@ const Profile: React.FC = () => {
|
||||
zipCode: profileData.zipCode || "",
|
||||
country: profileData.country || "",
|
||||
profileImage: profileData.profileImage || "",
|
||||
itemRequestNotificationRadius: profileData.itemRequestNotificationRadius || 10,
|
||||
itemRequestNotificationRadius:
|
||||
profileData.itemRequestNotificationRadius || 10,
|
||||
});
|
||||
setImagePreview(
|
||||
profileData.profileImage ? getImageUrl(profileData.profileImage) : null
|
||||
@@ -415,7 +435,10 @@ const Profile: React.FC = () => {
|
||||
setSuccess("Notification preferences saved successfully");
|
||||
setTimeout(() => setSuccess(null), 3000);
|
||||
} catch (err: any) {
|
||||
console.error("Notification preferences update error:", err.response?.data);
|
||||
console.error(
|
||||
"Notification preferences update error:",
|
||||
err.response?.data
|
||||
);
|
||||
const errorMessage =
|
||||
err.response?.data?.error ||
|
||||
err.response?.data?.message ||
|
||||
@@ -428,7 +451,10 @@ const Profile: React.FC = () => {
|
||||
e: React.ChangeEvent<HTMLSelectElement>
|
||||
) => {
|
||||
const { value } = e.target;
|
||||
setFormData((prev) => ({ ...prev, itemRequestNotificationRadius: parseInt(value) }));
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
itemRequestNotificationRadius: parseInt(value),
|
||||
}));
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
@@ -438,7 +464,10 @@ const Profile: React.FC = () => {
|
||||
setProfileData(response.data);
|
||||
updateUser(response.data);
|
||||
} catch (err: any) {
|
||||
console.error("Notification preferences update error:", err.response?.data);
|
||||
console.error(
|
||||
"Notification preferences update error:",
|
||||
err.response?.data
|
||||
);
|
||||
const errorMessage =
|
||||
err.response?.data?.error ||
|
||||
err.response?.data?.message ||
|
||||
@@ -560,8 +589,8 @@ const Profile: React.FC = () => {
|
||||
...addressFormData,
|
||||
...(coordinates && {
|
||||
latitude: coordinates.latitude,
|
||||
longitude: coordinates.longitude
|
||||
})
|
||||
longitude: coordinates.longitude,
|
||||
}),
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -614,7 +643,8 @@ const Profile: React.FC = () => {
|
||||
const { parsePlace } = useAddressAutocomplete();
|
||||
|
||||
// Handle place selection from autocomplete
|
||||
const handlePlaceSelect = useCallback((place: PlaceDetails) => {
|
||||
const handlePlaceSelect = useCallback(
|
||||
(place: PlaceDetails) => {
|
||||
const parsedAddress = parsePlace(place);
|
||||
if (parsedAddress) {
|
||||
setAddressFormData((prev) => ({
|
||||
@@ -624,7 +654,9 @@ const Profile: React.FC = () => {
|
||||
setAddressGeocodeSuccess(true);
|
||||
setTimeout(() => setAddressGeocodeSuccess(false), 3000);
|
||||
}
|
||||
}, [parsePlace]);
|
||||
},
|
||||
[parsePlace]
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
@@ -781,9 +813,13 @@ const Profile: React.FC = () => {
|
||||
type="button"
|
||||
className="btn btn-link text-primary p-0 ms-2"
|
||||
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>
|
||||
</div>
|
||||
{showPersonalInfo && (
|
||||
@@ -873,15 +909,18 @@ const Profile: React.FC = () => {
|
||||
<>
|
||||
{userAddresses.length === 0 && !showAddressForm ? (
|
||||
<div className="text-center py-3">
|
||||
<p className="text-muted mb-2">No saved addresses yet</p>
|
||||
<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
|
||||
Add an address or create your first listing to
|
||||
save one automatically
|
||||
</small>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{userAddresses.length > 0 && !showAddressForm && (
|
||||
{userAddresses.length > 0 &&
|
||||
!showAddressForm && (
|
||||
<>
|
||||
<div className="list-group list-group-flush mb-3">
|
||||
{userAddresses.map((address) => (
|
||||
@@ -911,7 +950,9 @@ const Profile: React.FC = () => {
|
||||
<button
|
||||
className="btn btn-outline-danger btn-sm"
|
||||
onClick={() =>
|
||||
handleDeleteAddress(address.id)
|
||||
handleDeleteAddress(
|
||||
address.id
|
||||
)
|
||||
}
|
||||
>
|
||||
<i className="bi bi-trash"></i>
|
||||
@@ -1225,10 +1266,13 @@ const Profile: React.FC = () => {
|
||||
<p className="mb-1 small">
|
||||
<strong>Owner:</strong>{" "}
|
||||
<span
|
||||
onClick={() => navigate(`/users/${rental.ownerId}`)}
|
||||
onClick={() =>
|
||||
navigate(`/users/${rental.ownerId}`)
|
||||
}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
{rental.owner.firstName} {rental.owner.lastName}
|
||||
{rental.owner.firstName}{" "}
|
||||
{rental.owner.lastName}
|
||||
</span>
|
||||
</p>
|
||||
)}
|
||||
@@ -1330,10 +1374,13 @@ const Profile: React.FC = () => {
|
||||
<p className="mb-1 small">
|
||||
<strong>Renter:</strong>{" "}
|
||||
<span
|
||||
onClick={() => navigate(`/users/${rental.renterId}`)}
|
||||
onClick={() =>
|
||||
navigate(`/users/${rental.renterId}`)
|
||||
}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
{rental.renter.firstName} {rental.renter.lastName}
|
||||
{rental.renter.firstName}{" "}
|
||||
{rental.renter.lastName}
|
||||
</span>
|
||||
</p>
|
||||
)}
|
||||
@@ -1460,8 +1507,64 @@ const Profile: React.FC = () => {
|
||||
<div className="card">
|
||||
<div className="card-body">
|
||||
<div className="mb-3">
|
||||
<label htmlFor="itemRequestNotificationRadius" className="form-label">
|
||||
Item Requests Notification Radius
|
||||
<div className="form-check">
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
id="enableItemRequestNotifications"
|
||||
checked={
|
||||
formData.itemRequestNotificationRadius !== null &&
|
||||
formData.itemRequestNotificationRadius !== undefined
|
||||
}
|
||||
onChange={async (e) => {
|
||||
const isEnabled = e.target.checked;
|
||||
const newRadius = isEnabled ? 10 : null; // Default to 10 miles when enabled
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
itemRequestNotificationRadius: newRadius,
|
||||
}));
|
||||
setError(null);
|
||||
|
||||
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>
|
||||
|
||||
{formData.itemRequestNotificationRadius !== null &&
|
||||
formData.itemRequestNotificationRadius !== undefined && (
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="itemRequestNotificationRadius"
|
||||
className="form-label"
|
||||
>
|
||||
Notification Radius
|
||||
</label>
|
||||
<select
|
||||
className="form-select"
|
||||
@@ -1477,9 +1580,11 @@ const Profile: React.FC = () => {
|
||||
<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
|
||||
You'll receive notifications for item requests within
|
||||
this distance from your primary address
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user