feat: reviews module — email request after checkout, API, job
- Migration 084: add review_token, review_email_sent_at, review_retry_sent_at to bookings - Backend route /api/hotels/:slug/reviews (GET list, PATCH reply/status) - Backend public routes /api/public/review/:token (GET config, POST submit) - email.ts: sendReviewRequestEmail template - jobs.ts: runReviewRequestJob — sends email N hours after checkout, retry after 24h - app.ts: register reviewsRoutes - api.ts: add ReviewApi, PublicReviewConfig types + reviews/publicReview methods - ReviewsPage: connect to real API, load/save settings, remove SMS option - GuestReviewPage: use :token param, fetch config from API, submit to API - App.tsx: route /review/:slug → /review/:token Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -477,6 +477,24 @@ export const api = {
|
||||
req<{ ok: boolean }>('PATCH', `/api/hotels/${slug}/hotel-settings`, data),
|
||||
},
|
||||
|
||||
// ── Reviews ───────────────────────────────────────────────────────────────
|
||||
reviews: {
|
||||
list: (slug: string) =>
|
||||
req<ReviewApi[]>('GET', `/api/hotels/${slug}/reviews`),
|
||||
|
||||
update: (slug: string, id: string, data: { reply?: string; isPublic?: boolean }) =>
|
||||
req<ReviewApi>('PATCH', `/api/hotels/${slug}/reviews/${id}`, data),
|
||||
},
|
||||
|
||||
// ── Public review (no auth) ───────────────────────────────────────────────
|
||||
publicReview: {
|
||||
getConfig: (token: string) =>
|
||||
req<PublicReviewConfig>('GET', `/api/public/review/${token}`),
|
||||
|
||||
submit: (token: string, data: { rating: number; text?: string }) =>
|
||||
req<{ ok: boolean }>('POST', `/api/public/review/${token}`, data),
|
||||
},
|
||||
|
||||
// ── Guests ────────────────────────────────────────────────────────────────
|
||||
guests: {
|
||||
list: (slug: string, q?: string, passport?: string) => {
|
||||
@@ -1843,3 +1861,33 @@ function toHotelPayload(h: HotelPayload): Record<string, unknown> {
|
||||
if (h.checkOutTime !== undefined) out.check_out_time = h.checkOutTime
|
||||
return out
|
||||
}
|
||||
|
||||
|
||||
export interface ReviewApi {
|
||||
id: string
|
||||
bookingId: string | null
|
||||
guestId: string | null
|
||||
guestName: string
|
||||
source: string
|
||||
rating: number
|
||||
text: string | null
|
||||
reply: string | null
|
||||
repliedAt: string | null
|
||||
isPublic: boolean
|
||||
roomNumber: string | null
|
||||
bookingSource: string | null
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export interface PublicReviewConfig {
|
||||
hotelName: string
|
||||
logoLetter: string
|
||||
welcomeText: string
|
||||
color: string
|
||||
redirectThreshold: number
|
||||
showTextField: boolean
|
||||
platforms: { name: string; url: string }[]
|
||||
guestName: string
|
||||
bookingSource: string
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user