feat: email field for QR review replies, internal note if no email
Some checks failed
Deploy to Production / deploy (push) Has been cancelled
Some checks failed
Deploy to Production / deploy (push) Has been cancelled
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -56,7 +56,7 @@ const reviewsRoutes: FastifyPluginAsync = async (fastify) => {
|
||||
)
|
||||
|
||||
// ── PATCH /api/hotels/:slug/reviews/:id ───────────────────────────────────
|
||||
fastify.patch<SlugIdParam & { Body: { reply?: string; isPublic?: boolean; rejected?: boolean } }>(
|
||||
fastify.patch<SlugIdParam & { Body: { reply?: string; isPublic?: boolean; rejected?: boolean; guestEmail?: string } }>(
|
||||
'/api/hotels/:slug/reviews/:id',
|
||||
{ onRequest: [fastify.authenticate] },
|
||||
async (request, reply) => {
|
||||
@@ -67,7 +67,7 @@ const reviewsRoutes: FastifyPluginAsync = async (fastify) => {
|
||||
const hotelId = await getHotelId(slug)
|
||||
if (!hotelId) return reply.code(404).send({ error: 'Hotel not found' })
|
||||
|
||||
const { reply: replyText, isPublic, rejected } = request.body
|
||||
const { reply: replyText, isPublic, rejected, guestEmail } = request.body
|
||||
const updates: string[] = []
|
||||
const vals: unknown[] = [id, hotelId]
|
||||
|
||||
@@ -93,16 +93,24 @@ const reviewsRoutes: FastifyPluginAsync = async (fastify) => {
|
||||
if (!rows[0]) return reply.code(404).send({ error: 'Review not found' })
|
||||
|
||||
// Send email to guest when manager adds a reply
|
||||
if (replyText && rows[0].booking_id) {
|
||||
const { rows: bRows } = await db.query<{
|
||||
guest_email: string | null; guest_name: string | null
|
||||
}>(
|
||||
`SELECT guest_email, guest_name FROM bookings WHERE id = $1`,
|
||||
[rows[0].booking_id],
|
||||
)
|
||||
const guestEmail = bRows[0]?.guest_email
|
||||
if (guestEmail) {
|
||||
const { rows: hRows } = await db.query<{ name: string }>(
|
||||
if (replyText) {
|
||||
// Determine email: from booking (direct reviews) or manually entered (QR reviews)
|
||||
let emailTo: string | null = guestEmail?.trim() || null
|
||||
let guestFirstName = ''
|
||||
|
||||
if (!emailTo && rows[0].booking_id) {
|
||||
const { rows: bRows } = await db.query<{
|
||||
guest_email: string | null; guest_name: string | null
|
||||
}>(
|
||||
`SELECT guest_email, guest_name FROM bookings WHERE id = $1`,
|
||||
[rows[0].booking_id],
|
||||
)
|
||||
emailTo = bRows[0]?.guest_email ?? null
|
||||
guestFirstName = extractFirstName(bRows[0]?.guest_name ?? null)
|
||||
}
|
||||
|
||||
if (emailTo) {
|
||||
const { rows: hRows } = await db.query<{ name: string; color: string }>(
|
||||
`SELECT h.name,
|
||||
COALESCE((SELECT value #>> '{}' FROM hotel_settings
|
||||
WHERE hotel_id = h.id AND key = 'review_brand_color'), '#2563eb') AS color
|
||||
@@ -110,10 +118,10 @@ const reviewsRoutes: FastifyPluginAsync = async (fastify) => {
|
||||
[hotelId],
|
||||
)
|
||||
sendReviewReplyEmail({
|
||||
to: guestEmail,
|
||||
guestFirstName: extractFirstName(bRows[0]?.guest_name ?? null),
|
||||
hotelName: (hRows[0] as unknown as { name: string; color: string })?.name ?? '',
|
||||
brandColor: (hRows[0] as unknown as { name: string; color: string })?.color ?? '#2563eb',
|
||||
to: emailTo,
|
||||
guestFirstName,
|
||||
hotelName: hRows[0]?.name ?? '',
|
||||
brandColor: hRows[0]?.color ?? '#2563eb',
|
||||
replyText,
|
||||
originalRating: rows[0].rating,
|
||||
}).catch(err => console.error('[reviews] reply email error:', err))
|
||||
|
||||
Reference in New Issue
Block a user