fixing bugs with item notification radius
This commit is contained in:
@@ -240,7 +240,7 @@ router.get('/posts/:id', optionalAuth, async (req, res) => {
|
||||
// POST /api/forum/posts - Create new post
|
||||
router.post('/posts', authenticateToken, uploadForumPostImages, async (req, res) => {
|
||||
try {
|
||||
let { title, content, category, tags, zipCode } = req.body;
|
||||
let { title, content, category, tags, zipCode, latitude: providedLat, longitude: providedLng } = req.body;
|
||||
|
||||
// Parse tags if they come as JSON string (from FormData)
|
||||
if (typeof tags === 'string') {
|
||||
@@ -258,26 +258,53 @@ router.post('/posts', authenticateToken, uploadForumPostImages, async (req, res)
|
||||
let latitude = null;
|
||||
let longitude = null;
|
||||
|
||||
// Geocode zip code for item requests
|
||||
// Use provided coordinates if available, otherwise geocode zip code
|
||||
if (category === 'item_request' && zipCode) {
|
||||
try {
|
||||
const geocodeResult = await googleMapsService.geocodeAddress(zipCode);
|
||||
latitude = geocodeResult.latitude;
|
||||
longitude = geocodeResult.longitude;
|
||||
// If coordinates were provided from a saved address, use them directly
|
||||
if (providedLat && providedLng) {
|
||||
latitude = parseFloat(providedLat);
|
||||
longitude = parseFloat(providedLng);
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Geocoded zip code for item request", {
|
||||
reqLogger.info("Using provided coordinates for item request", {
|
||||
zipCode,
|
||||
latitude,
|
||||
longitude
|
||||
longitude,
|
||||
source: 'saved_address'
|
||||
});
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Geocoding failed for item request", {
|
||||
error: error.message,
|
||||
zipCode
|
||||
});
|
||||
// Continue without coordinates - post will still be created
|
||||
} else {
|
||||
// Otherwise, geocode the zip code
|
||||
try {
|
||||
const geocodeResult = await googleMapsService.geocodeAddress(zipCode);
|
||||
|
||||
// Check if geocoding was successful
|
||||
if (geocodeResult.error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Geocoding failed for item request", {
|
||||
error: geocodeResult.error,
|
||||
status: geocodeResult.status,
|
||||
zipCode
|
||||
});
|
||||
} else if (geocodeResult.latitude && geocodeResult.longitude) {
|
||||
latitude = geocodeResult.latitude;
|
||||
longitude = geocodeResult.longitude;
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Geocoded zip code for item request", {
|
||||
zipCode,
|
||||
latitude,
|
||||
longitude,
|
||||
source: 'geocoded'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Geocoding failed for item request", {
|
||||
error: error.message,
|
||||
zipCode
|
||||
});
|
||||
// Continue without coordinates - post will still be created
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,6 +359,13 @@ router.post('/posts', authenticateToken, uploadForumPostImages, async (req, res)
|
||||
if (category === 'item_request' && latitude && longitude) {
|
||||
(async () => {
|
||||
try {
|
||||
logger.info("Starting item request notifications", {
|
||||
postId: post.id,
|
||||
latitude,
|
||||
longitude,
|
||||
zipCode
|
||||
});
|
||||
|
||||
// Find all users within maximum radius (100 miles)
|
||||
const nearbyUsers = await locationService.findUsersInRadius(
|
||||
latitude,
|
||||
@@ -339,10 +373,17 @@ router.post('/posts', authenticateToken, uploadForumPostImages, async (req, res)
|
||||
100
|
||||
);
|
||||
|
||||
logger.info("Found nearby users", {
|
||||
postId: post.id,
|
||||
count: nearbyUsers.length,
|
||||
users: nearbyUsers.map(u => ({ id: u.id, distance: u.distance }))
|
||||
});
|
||||
|
||||
const postAuthor = await User.findByPk(req.user.id);
|
||||
|
||||
let notificationsSent = 0;
|
||||
let usersChecked = 0;
|
||||
let usersSkipped = 0;
|
||||
|
||||
for (const user of nearbyUsers) {
|
||||
// Don't notify the requester
|
||||
@@ -356,6 +397,17 @@ router.post('/posts', authenticateToken, uploadForumPostImages, async (req, res)
|
||||
|
||||
const userPreferredRadius = userProfile?.itemRequestNotificationRadius || 10;
|
||||
|
||||
logger.info("Checking user notification eligibility", {
|
||||
postId: post.id,
|
||||
userId: user.id,
|
||||
userEmail: user.email,
|
||||
userCoordinates: { lat: user.latitude, lng: user.longitude },
|
||||
postCoordinates: { lat: latitude, lng: longitude },
|
||||
userDistance: user.distance,
|
||||
userPreferredRadius,
|
||||
willNotify: parseFloat(user.distance) <= userPreferredRadius
|
||||
});
|
||||
|
||||
// Only notify if within user's preferred radius
|
||||
if (parseFloat(user.distance) <= userPreferredRadius) {
|
||||
try {
|
||||
@@ -366,6 +418,11 @@ router.post('/posts', authenticateToken, uploadForumPostImages, async (req, res)
|
||||
user.distance
|
||||
);
|
||||
notificationsSent++;
|
||||
logger.info("Sent notification to user", {
|
||||
postId: post.id,
|
||||
userId: user.id,
|
||||
distance: user.distance
|
||||
});
|
||||
} catch (emailError) {
|
||||
logger.error("Failed to send item request notification", {
|
||||
error: emailError.message,
|
||||
@@ -373,14 +430,17 @@ router.post('/posts', authenticateToken, uploadForumPostImages, async (req, res)
|
||||
postId: post.id
|
||||
});
|
||||
}
|
||||
} else {
|
||||
usersSkipped++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Item request notifications sent", {
|
||||
logger.info("Item request notifications complete", {
|
||||
postId: post.id,
|
||||
totalNearbyUsers: nearbyUsers.length,
|
||||
usersChecked,
|
||||
usersSkipped,
|
||||
notificationsSent
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -391,6 +451,13 @@ router.post('/posts', authenticateToken, uploadForumPostImages, async (req, res)
|
||||
});
|
||||
}
|
||||
})();
|
||||
} else if (category === 'item_request') {
|
||||
logger.warn("Item request created without location", {
|
||||
postId: post.id,
|
||||
zipCode,
|
||||
hasLatitude: !!latitude,
|
||||
hasLongitude: !!longitude
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
|
||||
@@ -20,6 +20,12 @@ class LocationService {
|
||||
throw new Error('Radius must be between 1 and 100 miles');
|
||||
}
|
||||
|
||||
console.log('Finding users in radius:', {
|
||||
centerLatitude: latitude,
|
||||
centerLongitude: longitude,
|
||||
radiusMiles
|
||||
});
|
||||
|
||||
try {
|
||||
// Haversine formula:
|
||||
// distance = 3959 * acos(cos(radians(lat1)) * cos(radians(lat2))
|
||||
@@ -27,26 +33,28 @@ class LocationService {
|
||||
// + sin(radians(lat1)) * sin(radians(lat2)))
|
||||
// Note: 3959 is Earth's radius in miles
|
||||
const query = `
|
||||
SELECT
|
||||
u.id,
|
||||
u.email,
|
||||
u."firstName",
|
||||
u."lastName",
|
||||
ua.latitude,
|
||||
ua.longitude,
|
||||
(3959 * acos(
|
||||
LEAST(1.0,
|
||||
cos(radians(:lat)) * cos(radians(ua.latitude))
|
||||
* cos(radians(ua.longitude) - radians(:lng))
|
||||
+ sin(radians(:lat)) * sin(radians(ua.latitude))
|
||||
)
|
||||
)) AS distance
|
||||
FROM "Users" u
|
||||
INNER JOIN "UserAddresses" ua ON u.id = ua."userId"
|
||||
WHERE ua."isPrimary" = true
|
||||
AND ua.latitude IS NOT NULL
|
||||
AND ua.longitude IS NOT NULL
|
||||
HAVING distance < :radiusMiles
|
||||
SELECT * FROM (
|
||||
SELECT
|
||||
u.id,
|
||||
u.email,
|
||||
u."firstName",
|
||||
u."lastName",
|
||||
ua.latitude,
|
||||
ua.longitude,
|
||||
(3959 * acos(
|
||||
LEAST(1.0,
|
||||
cos(radians(:lat)) * cos(radians(ua.latitude))
|
||||
* cos(radians(ua.longitude) - radians(:lng))
|
||||
+ sin(radians(:lat)) * sin(radians(ua.latitude))
|
||||
)
|
||||
)) AS distance
|
||||
FROM "Users" u
|
||||
INNER JOIN "UserAddresses" ua ON u.id = ua."userId"
|
||||
WHERE ua."isPrimary" = true
|
||||
AND ua.latitude IS NOT NULL
|
||||
AND ua.longitude IS NOT NULL
|
||||
) AS user_distances
|
||||
WHERE distance < :radiusMiles
|
||||
ORDER BY distance ASC
|
||||
`;
|
||||
|
||||
@@ -59,6 +67,13 @@ class LocationService {
|
||||
type: QueryTypes.SELECT
|
||||
});
|
||||
|
||||
console.log('Users found in radius:', users.map(u => ({
|
||||
id: u.id,
|
||||
userLat: u.latitude,
|
||||
userLng: u.longitude,
|
||||
distance: parseFloat(u.distance).toFixed(2)
|
||||
})));
|
||||
|
||||
return users.map(user => ({
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
|
||||
@@ -1,285 +1,295 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>Item Request Near You</title>
|
||||
<style>
|
||||
/* Reset styles */
|
||||
body, table, td, p, a, li, blockquote {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
table, td {
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
}
|
||||
img {
|
||||
-ms-interpolation-mode: bicubic;
|
||||
}
|
||||
/* Reset styles */
|
||||
body,
|
||||
table,
|
||||
td,
|
||||
p,
|
||||
a,
|
||||
li,
|
||||
blockquote {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
table,
|
||||
td {
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
}
|
||||
img {
|
||||
-ms-interpolation-mode: bicubic;
|
||||
}
|
||||
|
||||
/* Base styles */
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100% !important;
|
||||
min-width: 100%;
|
||||
height: 100%;
|
||||
background-color: #f8f9fa;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #212529;
|
||||
}
|
||||
/* Base styles */
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100% !important;
|
||||
min-width: 100%;
|
||||
height: 100%;
|
||||
background-color: #f8f9fa;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
/* Container */
|
||||
/* Container */
|
||||
.email-container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background-color: #ffffff;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 40px 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
|
||||
.tagline {
|
||||
color: #e0d4f7;
|
||||
font-size: 14px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
/* Content */
|
||||
.content {
|
||||
padding: 40px 30px;
|
||||
}
|
||||
|
||||
.content h1 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 20px 0;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.content h2 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin: 30px 0 15px 0;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.content p {
|
||||
margin: 0 0 16px 0;
|
||||
color: #6c757d;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.content strong {
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
/* Button */
|
||||
.button {
|
||||
display: inline-block;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: #ffffff !important;
|
||||
text-decoration: none;
|
||||
padding: 16px 32px;
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
/* Info box */
|
||||
.info-box {
|
||||
background-color: #fff3cd;
|
||||
border-left: 4px solid #ffc107;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
border-radius: 0 6px 6px 0;
|
||||
}
|
||||
|
||||
.info-box p {
|
||||
margin: 0;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
/* Item request box */
|
||||
.request-box {
|
||||
background-color: #f8f9fa;
|
||||
border-left: 4px solid #667eea;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
border-radius: 0 6px 6px 0;
|
||||
}
|
||||
|
||||
.request-box .title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
margin: 0 0 15px 0;
|
||||
}
|
||||
|
||||
.request-box .description {
|
||||
color: #212529;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.request-box .requester {
|
||||
font-size: 14px;
|
||||
color: #6c757d;
|
||||
margin-top: 15px;
|
||||
padding-top: 15px;
|
||||
border-top: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
/* Help section */
|
||||
.help-section {
|
||||
background-color: #d4edda;
|
||||
border: 1px solid #c3e6cb;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
border-radius: 6px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.help-section p {
|
||||
margin: 0;
|
||||
color: #155724;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
background-color: #f8f9fa;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
border-top: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 14px;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
color: #667eea;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media only screen and (max-width: 600px) {
|
||||
.email-container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background-color: #ffffff;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 40px 30px;
|
||||
text-align: center;
|
||||
.header,
|
||||
.content,
|
||||
.footer {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
|
||||
.tagline {
|
||||
color: #e0d4f7;
|
||||
font-size: 14px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
/* Content */
|
||||
.content {
|
||||
padding: 40px 30px;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.content h1 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 20px 0;
|
||||
color: #212529;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.content h2 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin: 30px 0 15px 0;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.content p {
|
||||
margin: 0 0 16px 0;
|
||||
color: #6c757d;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.content strong {
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
/* Button */
|
||||
.button {
|
||||
display: inline-block;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: #ffffff !important;
|
||||
text-decoration: none;
|
||||
padding: 16px 32px;
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
transition: all 0.3s ease;
|
||||
display: block;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
/* Distance badge */
|
||||
.distance-badge {
|
||||
display: inline-block;
|
||||
background-color: #e7f3ff;
|
||||
color: #0066cc;
|
||||
padding: 6px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
/* Info box */
|
||||
.info-box {
|
||||
background-color: #fff3cd;
|
||||
border-left: 4px solid #ffc107;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
border-radius: 0 6px 6px 0;
|
||||
}
|
||||
|
||||
.info-box p {
|
||||
margin: 0;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
/* Item request box */
|
||||
.request-box {
|
||||
background-color: #f8f9fa;
|
||||
border-left: 4px solid #667eea;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
border-radius: 0 6px 6px 0;
|
||||
}
|
||||
|
||||
.request-box .title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
margin: 0 0 15px 0;
|
||||
}
|
||||
|
||||
.request-box .description {
|
||||
color: #212529;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.request-box .requester {
|
||||
font-size: 14px;
|
||||
color: #6c757d;
|
||||
margin-top: 15px;
|
||||
padding-top: 15px;
|
||||
border-top: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
/* Help section */
|
||||
.help-section {
|
||||
background-color: #d4edda;
|
||||
border: 1px solid #c3e6cb;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
border-radius: 6px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.help-section p {
|
||||
margin: 0;
|
||||
color: #155724;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
background-color: #f8f9fa;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
border-top: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 14px;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
color: #667eea;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media only screen and (max-width: 600px) {
|
||||
.email-container {
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.header, .content, .footer {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.content h1 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.request-box {
|
||||
padding: 15px;
|
||||
}
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
</head>
|
||||
<body>
|
||||
<div class="email-container">
|
||||
<div class="header">
|
||||
<div class="logo">RentAll</div>
|
||||
<div class="tagline">Item Request Near You</div>
|
||||
<div class="header">
|
||||
<div class="logo">RentAll</div>
|
||||
<div class="tagline">Item Request Near You</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<p>Hi {{recipientName}},</p>
|
||||
|
||||
<h1>Someone nearby is looking for an item!</h1>
|
||||
|
||||
<p>
|
||||
<strong>{{requesterName}}</strong> posted an item request in your
|
||||
area. You might be able to help!
|
||||
</p>
|
||||
|
||||
<div class="request-box">
|
||||
<div class="title">{{itemRequested}}</div>
|
||||
<div class="description">{{requestDescription}}</div>
|
||||
<div class="requester">Posted by {{requesterName}}</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<p>Hi {{recipientName}},</p>
|
||||
|
||||
<h1>Someone nearby is looking for an item!</h1>
|
||||
|
||||
<div class="distance-badge">📍 About {{distance}} miles away</div>
|
||||
|
||||
<p><strong>{{requesterName}}</strong> posted an item request in your area. You might be able to help!</p>
|
||||
|
||||
<div class="request-box">
|
||||
<div class="title">{{itemRequested}}</div>
|
||||
<div class="description">{{requestDescription}}</div>
|
||||
<div class="requester">Posted by {{requesterName}}</div>
|
||||
</div>
|
||||
|
||||
<div class="help-section">
|
||||
<p>💡 Have this item? You can help a neighbor and potentially earn money!</p>
|
||||
</div>
|
||||
|
||||
<a href="{{postUrl}}" class="button">View Request & Respond</a>
|
||||
|
||||
<p>Click the button above to see the full details and offer your help if you have the item they're looking for.</p>
|
||||
|
||||
<div class="info-box">
|
||||
<p><strong>Why did I get this?</strong> You're receiving this notification because you're located within the requested area for this item. Help build your local community by responding to nearby requests!</p>
|
||||
</div>
|
||||
<div class="help-section">
|
||||
<p>💡 Have this item? You can help a neighbor!</p>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p><strong>RentAll</strong></p>
|
||||
<p>You received this email because someone near you posted an item request.</p>
|
||||
<p>If you have any questions, please contact our support team.</p>
|
||||
<p>© 2024 RentAll. All rights reserved.</p>
|
||||
<a href="{{postUrl}}" class="button">View Request & Respond</a>
|
||||
|
||||
<p>
|
||||
Click the button above to see the full details and offer your help if
|
||||
you have the item they're looking for.
|
||||
</p>
|
||||
|
||||
<div class="info-box">
|
||||
<p>
|
||||
<strong>Why did I get this?</strong> You're receiving this
|
||||
notification because you're located within the requested area for
|
||||
this item. Help build your local community by responding to nearby
|
||||
requests!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p><strong>RentAll</strong></p>
|
||||
<p>
|
||||
You received this email because someone near you posted an item
|
||||
request.
|
||||
</p>
|
||||
<p>If you have any questions, please contact our support team.</p>
|
||||
<p>© 2024 RentAll. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user