feat: email field for QR review replies, internal note if no email
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:
2026-04-21 17:37:22 +03:00
parent d83ffcb3c7
commit 8ab71b1e45
3 changed files with 53 additions and 26 deletions

View File

@@ -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))