add booking rating templates

Made-with: Cursor
This commit is contained in:
“kirukib” 2026-04-02 11:11:29 +03:00
parent d22fc69589
commit c5b868e201
3 changed files with 497 additions and 2 deletions

View File

@ -83,7 +83,7 @@ export default function App() {
}, },
grid: { grid: {
display: 'grid', display: 'grid',
gridTemplateColumns: '420px 1fr', gridTemplateColumns: 'repeat(auto-fit, minmax(360px, 1fr))',
gap: 16, gap: 16,
alignItems: 'start', alignItems: 'start',
}, },

View File

@ -11,7 +11,7 @@ export function EmailPreview({ html, loading }: { html: string; loading: boolean
<iframe <iframe
title="Email preview" title="Email preview"
srcDoc={html} srcDoc={html}
style={{ width: '100%', height: 720, border: '0' }} style={{ width: '100%', height: 'min(720px, 70vh)', minHeight: 420, border: '0' }}
/> />
) : ( ) : (
<div style={{ padding: 18, opacity: 0.7 }}>Generate a preview to see the email here.</div> <div style={{ padding: 18, opacity: 0.7 }}>Generate a preview to see the email here.</div>

View File

@ -584,6 +584,501 @@ const templates: EmailTemplateDefinition[] = [
) )
}, },
}, },
{
id: 'booking_successful',
name: 'Successful Booking',
subjectTemplate: 'Your stay is confirmed, {{guestName}} ({{bookingId}})',
ctaLabel: 'View booking',
ctaHrefTemplate:
'https://shitaye-hotel.kirubeljkl679.workers.dev/?bookingId={{bookingId}}',
variables: [
{ key: 'guestName', label: 'Guest name', defaultValue: 'Sarah M.' },
{ key: 'bookingId', label: 'Booking ID', defaultValue: 'SH-8421' },
{ key: 'roomType', label: 'Room type', defaultValue: 'Junior Studio' },
{ key: 'checkInDate', label: 'Check-in date', inputType: 'date', defaultValue: '2026-04-15' },
{ key: 'checkOutDate', label: 'Check-out date', inputType: 'date', defaultValue: '2026-04-18' },
],
Component: function BookingSuccessfulEmail({ brand, data }) {
const guestName = data.guestName || 'Guest'
const bookingId = data.bookingId || ''
const roomType = data.roomType || 'Room'
const checkInDate = data.checkInDate || '—'
const checkOutDate = data.checkOutDate || '—'
const href = interpolate('https://shitaye-hotel.kirubeljkl679.workers.dev/?bookingId={{bookingId}}', { bookingId })
return (
<EmailShell
brand={brand}
title="Your booking is confirmed"
previewText={`Hi ${guestName} — your booking ${bookingId} is confirmed.`}
>
<Heading style={{ fontSize: 22, margin: '0 0 8px', color: brand.textColor }}>
Stay confirmed
</Heading>
<Text style={{ margin: '0 0 10px', color: brand.textColor }}>
Hi {guestName},
</Text>
<Text style={{ margin: '0 0 14px', color: brand.textColor, lineHeight: '24px' }}>
Were excited to host you at <strong>{brand.hotelName}</strong>. Your reservation for a{' '}
<strong>{roomType}</strong> is confirmed.
</Text>
<Section style={blockStyle(brand)}>
<Section style={{ margin: '0 0 10px' }}>
<Field brand={brand} label="Booking ID" value={bookingId} />
</Section>
<Hr style={{ borderColor: brand.secondaryColor, opacity: 0.15, margin: '10px 0' }} />
<Section style={{ margin: '0 0 10px' }}>
<Field brand={brand} label="Room type" value={roomType} />
</Section>
<Hr style={{ borderColor: brand.secondaryColor, opacity: 0.15, margin: '10px 0' }} />
<Section>
<Field brand={brand} label="Dates" value={`${checkInDate}${checkOutDate}`} />
</Section>
</Section>
<PrimaryCTA brand={brand} href={href} label="View booking" />
<Text style={{ margin: '16px 0 0', color: brand.textColor, lineHeight: '22px' }}>
If you need any assistance before arrival, contact reception using the details in the footer.
</Text>
</EmailShell>
)
},
},
{
id: 'booking_canceled',
name: 'Booking Cancelled (alt)',
subjectTemplate: 'Your booking has been cancelled, {{guestName}}',
ctaLabel: 'Book again',
ctaHrefTemplate: 'https://shitaye-hotel.kirubeljkl679.workers.dev/',
variables: [
{ key: 'guestName', label: 'Guest name', defaultValue: 'Hanna T.' },
{ key: 'bookingId', label: 'Booking ID', defaultValue: 'SH-8421' },
{ key: 'cancellationDate', label: 'Cancellation date', inputType: 'date', defaultValue: '2026-04-10' },
{ key: 'cancellationReason', label: 'Cancellation reason', defaultValue: 'Schedule change' },
],
Component: function BookingCanceledEmail({ brand, data }) {
const guestName = data.guestName || 'Guest'
const bookingId = data.bookingId || ''
const cancellationDate = data.cancellationDate || '—'
const cancellationReason = data.cancellationReason || ''
return (
<EmailShell
brand={brand}
title="Booking cancelled"
previewText={`Hi ${guestName} — booking ${bookingId} has been cancelled.`}
>
<Heading style={{ fontSize: 22, margin: '0 0 8px', color: brand.textColor }}>
Booking cancelled
</Heading>
<Text style={{ margin: '0 0 14px', color: brand.textColor, lineHeight: '24px' }}>
Hi {guestName}, were letting you know that booking <strong>{bookingId}</strong> has been cancelled.
</Text>
<Section style={blockStyle(brand)}>
<Section style={{ margin: '0 0 10px' }}>
<Field brand={brand} label="Cancelled on" value={cancellationDate} />
</Section>
<Hr style={{ borderColor: brand.secondaryColor, opacity: 0.15, margin: '10px 0' }} />
<Section>
<Text style={{ ...valueStyle(brand), fontSize: 13 }}>{cancellationReason}</Text>
</Section>
</Section>
<PrimaryCTA
brand={brand}
href={'https://shitaye-hotel.kirubeljkl679.workers.dev/'}
label="Book again"
/>
<Text style={{ margin: '16px 0 0', color: brand.textColor, lineHeight: '22px' }}>
If you have questions, reply to this email or contact reception.
</Text>
</EmailShell>
)
},
},
{
id: 'laundry_order_confirmation',
name: 'Laundry Order Confirmation',
subjectTemplate: 'Your laundry order {{orderId}} is confirmed, {{guestName}}',
ctaLabel: 'Track laundry',
ctaHrefTemplate: 'https://shitaye-hotel.kirubeljkl679.workers.dev/laundry/status?order={{orderId}}',
variables: [
{ key: 'guestName', label: 'Guest name', defaultValue: 'Hanna T.' },
{ key: 'orderId', label: 'Order ID', defaultValue: 'LA-55902' },
{ key: 'serviceType', label: 'Service type', defaultValue: 'Wash & Fold' },
{ key: 'pickupWindow', label: 'Pickup window', defaultValue: 'Today 6:00 PM - 7:30 PM' },
{ key: 'deliveryWindow', label: 'Delivery window', defaultValue: 'Tomorrow 2:00 PM - 4:00 PM' },
{ key: 'itemsDescription', label: 'Laundry notes / items', defaultValue: 'Shirts (6) + Trousers (3)' },
],
Component: function LaundryOrderConfirmationEmail({ brand, data }) {
const guestName = data.guestName || 'Guest'
const orderId = data.orderId || ''
const serviceType = data.serviceType || ''
const pickupWindow = data.pickupWindow || ''
const deliveryWindow = data.deliveryWindow || ''
const itemsDescription = data.itemsDescription || ''
const href = interpolate(
'https://shitaye-hotel.kirubeljkl679.workers.dev/laundry/status?order={{orderId}}',
{ orderId },
)
return (
<EmailShell
brand={brand}
title="Laundry order confirmed"
previewText={`Hi ${guestName} — your laundry order ${orderId} has been confirmed.`}
>
<Heading style={{ fontSize: 22, margin: '0 0 8px', color: brand.textColor }}>
Laundry order received
</Heading>
<Text style={{ margin: '0 0 14px', color: brand.textColor, lineHeight: '24px' }}>
Hi {guestName}, weve received your laundry request ({orderId}).
</Text>
<Section style={blockStyle(brand)}>
<Section style={{ margin: '0 0 10px' }}>
<Field brand={brand} label="Service" value={serviceType} />
</Section>
<Hr style={{ borderColor: brand.secondaryColor, opacity: 0.15, margin: '10px 0' }} />
<Section style={{ margin: '0 0 10px' }}>
<Field brand={brand} label="Pickup" value={pickupWindow} />
</Section>
<Hr style={{ borderColor: brand.secondaryColor, opacity: 0.15, margin: '10px 0' }} />
<Section style={{ margin: '0 0 10px' }}>
<Field brand={brand} label="Delivery" value={deliveryWindow} />
</Section>
<Hr style={{ borderColor: brand.secondaryColor, opacity: 0.15, margin: '10px 0' }} />
<Section>
<Text style={{ ...valueStyle(brand), fontSize: 13 }}>{itemsDescription}</Text>
</Section>
</Section>
<PrimaryCTA brand={brand} href={href} label="Track your laundry" />
<Text style={{ margin: '16px 0 0', color: brand.textColor, lineHeight: '22px' }}>
Well send updates as your order moves through pickup and delivery.
</Text>
</EmailShell>
)
},
},
{
id: 'room_service_order_confirmation',
name: 'Room Service Order Confirmation',
subjectTemplate: 'Your room service order {{orderId}} is confirmed, {{guestName}}',
ctaLabel: 'Track order',
ctaHrefTemplate: 'https://shitaye-hotel.kirubeljkl679.workers.dev/room-service/status?order={{orderId}}',
variables: [
{ key: 'guestName', label: 'Guest name', defaultValue: 'Daniel K.' },
{ key: 'orderId', label: 'Order ID', defaultValue: 'RS-1029' },
{ key: 'items', label: 'Items / notes', defaultValue: 'Breakfast tray (1) + Coffee (2)' },
{ key: 'orderTime', label: 'Order time', defaultValue: 'Today 8:15 PM' },
{ key: 'etaWindow', label: 'Estimated delivery', defaultValue: 'Within 25-35 minutes' },
],
Component: function RoomServiceOrderConfirmationEmail({ brand, data }) {
const guestName = data.guestName || 'Guest'
const orderId = data.orderId || ''
const items = data.items || ''
const orderTime = data.orderTime || ''
const etaWindow = data.etaWindow || ''
const href = interpolate(
'https://shitaye-hotel.kirubeljkl679.workers.dev/room-service/status?order={{orderId}}',
{ orderId },
)
return (
<EmailShell
brand={brand}
title="Room service order confirmed"
previewText={`Hi ${guestName} — your order ${orderId} is confirmed.`}
>
<Heading style={{ fontSize: 22, margin: '0 0 8px', color: brand.textColor }}>
Room service confirmed
</Heading>
<Text style={{ margin: '0 0 14px', color: brand.textColor, lineHeight: '24px' }}>
Hi {guestName}, weve received your room service order (<strong>{orderId}</strong>).
</Text>
<Section style={blockStyle(brand)}>
<Section style={{ margin: '0 0 10px' }}>
<Text style={{ ...labelStyle(brand), fontSize: 12, margin: 0 }}>Order time</Text>
<Text style={{ ...valueStyle(brand), fontSize: 14 }}>{orderTime}</Text>
</Section>
<Hr style={{ borderColor: brand.secondaryColor, opacity: 0.15, margin: '10px 0' }} />
<Section style={{ margin: '0 0 10px' }}>
<Text style={{ ...labelStyle(brand), fontSize: 12, margin: 0 }}>Items / notes</Text>
<Text style={{ ...valueStyle(brand), fontSize: 14 }}>{items}</Text>
</Section>
<Hr style={{ borderColor: brand.secondaryColor, opacity: 0.15, margin: '10px 0' }} />
<Section>
<Text style={{ ...labelStyle(brand), fontSize: 12, margin: 0 }}>Estimated delivery</Text>
<Text style={{ ...valueStyle(brand), fontSize: 14 }}>{etaWindow}</Text>
</Section>
</Section>
<PrimaryCTA brand={brand} href={href} label="Track your order" />
<Text style={{ margin: '16px 0 0', color: brand.textColor, lineHeight: '22px' }}>
If anything changes, reply to this email and well assist you.
</Text>
</EmailShell>
)
},
},
{
id: 'prize_winning',
name: 'Prize Winning',
subjectTemplate: 'Congratulations {{guestName}}! You won {{prizeName}}',
ctaLabel: 'Claim prize',
ctaHrefTemplate: 'https://shitaye-hotel.kirubeljkl679.workers.dev/prizes/claim?raffle={{raffleName}}&prize={{prizeName}}',
variables: [
{ key: 'guestName', label: 'Guest name', defaultValue: 'Sarah M.' },
{ key: 'raffleName', label: 'Raffle name', defaultValue: 'FeastVille Lucky Draw' },
{ key: 'prizeName', label: 'Prize name', defaultValue: 'Spa Session for Two' },
{ key: 'claimByDate', label: 'Claim by', inputType: 'date', defaultValue: '2026-05-17' },
{
key: 'claimSteps',
label: 'Claim instructions',
defaultValue:
'1) Reply to this email; 2) Provide your entry code; 3) Our team will confirm your appointment',
},
],
Component: function PrizeWinningEmail({ brand, data }) {
const guestName = data.guestName || 'Guest'
const raffleName = data.raffleName || 'Raffle'
const prizeName = data.prizeName || 'Prize'
const claimByDate = data.claimByDate || '—'
const claimSteps = data.claimSteps || ''
const href = interpolate(
'https://shitaye-hotel.kirubeljkl679.workers.dev/prizes/claim?raffle={{raffleName}}&prize={{prizeName}}',
{ raffleName, prizeName },
)
return (
<EmailShell
brand={brand}
title="You won a prize"
previewText={`Hi ${guestName} — congratulations! You won ${prizeName}.`}
>
<Heading style={{ fontSize: 22, margin: '0 0 8px', color: brand.textColor }}>
Congratulations!
</Heading>
<Text style={{ margin: '0 0 14px', color: brand.textColor, lineHeight: '24px' }}>
Youve won <strong>{prizeName}</strong> from <strong>{raffleName}</strong>.
</Text>
<Section style={blockStyle(brand)}>
<Section style={{ margin: '0 0 10px' }}>
<Field brand={brand} label="Claim by" value={claimByDate} />
</Section>
<Hr style={{ borderColor: brand.secondaryColor, opacity: 0.15, margin: '10px 0' }} />
<Section>
<Text style={{ ...valueStyle(brand), fontSize: 13 }}>
{claimSteps.split(';').map((line, idx) => (
<React.Fragment key={idx}>
{line.trim()}
<br />
</React.Fragment>
))}
</Text>
</Section>
</Section>
<PrimaryCTA brand={brand} href={href} label="Claim your prize" />
<Text style={{ margin: '16px 0 0', color: brand.textColor, lineHeight: '22px' }}>
Need help? Contact us using the footer details.
</Text>
</EmailShell>
)
},
},
{
id: 'points_awarded',
name: 'Points Awarded',
subjectTemplate: 'You were awarded {{pointsAwarded}} points, {{guestName}}',
ctaLabel: 'View rewards',
ctaHrefTemplate: 'https://shitaye-hotel.kirubeljkl679.workers.dev/loyalty?points={{pointsAwarded}}',
variables: [
{ key: 'guestName', label: 'Guest name', defaultValue: 'Hanna T.' },
{ key: 'pointsAwarded', label: 'Points awarded', inputType: 'number', defaultValue: '120' },
{ key: 'newBalance', label: 'New points balance', inputType: 'number', defaultValue: '840' },
{ key: 'rewardDescription', label: 'Reward note', defaultValue: 'Thanks for staying with us. Keep booking to unlock premium perks!' },
{ key: 'expiryDate', label: 'Expiry date', inputType: 'date', defaultValue: '2027-04-30' },
],
Component: function PointsAwardedEmail({ brand, data }) {
const guestName = data.guestName || 'Guest'
const pointsAwarded = data.pointsAwarded || '0'
const newBalance = data.newBalance || '0'
const rewardDescription = data.rewardDescription || ''
const expiryDate = data.expiryDate || '—'
const href = interpolate(
'https://shitaye-hotel.kirubeljkl679.workers.dev/loyalty?points={{pointsAwarded}}',
{ pointsAwarded },
)
return (
<EmailShell
brand={brand}
title="Points awarded"
previewText={`Hi ${guestName} — you received ${pointsAwarded} points!`}
>
<Heading style={{ fontSize: 22, margin: '0 0 8px', color: brand.textColor }}>
Points update
</Heading>
<Text style={{ margin: '0 0 14px', color: brand.textColor, lineHeight: '24px' }}>
Hi {guestName}, youve been awarded <strong>{pointsAwarded}</strong> points. Your new balance is <strong>{newBalance}</strong>.
</Text>
<Section style={blockStyle(brand)}>
<Section style={{ margin: '0 0 10px' }}>
<Field brand={brand} label="Expiry" value={expiryDate} />
</Section>
<Hr style={{ borderColor: brand.secondaryColor, opacity: 0.15, margin: '10px 0' }} />
<Section>
<Text style={{ ...valueStyle(brand), fontSize: 13 }}>{rewardDescription}</Text>
</Section>
</Section>
<PrimaryCTA brand={brand} href={href} label="View your rewards" />
<Text style={{ margin: '16px 0 0', color: brand.textColor, lineHeight: '22px' }}>
Rewards are our way of saying thank you for choosing {brand.hotelName}.
</Text>
</EmailShell>
)
},
},
{
id: 'booking_rating_google',
name: 'Booking Rating (Google)',
subjectTemplate: 'Rate your stay at {{hotelName}} — {{guestName}}',
ctaLabel: 'Leave a Google review',
ctaHrefTemplate: '{{googleReviewUrl}}',
variables: [
{ key: 'guestName', label: 'Guest name', defaultValue: 'Sarah M.' },
{ key: 'bookingId', label: 'Booking ID', defaultValue: 'SH-8421' },
{ key: 'googleReviewUrl', label: 'Google review URL', defaultValue: 'https://g.page/r/your-hotel/review' },
{ key: 'stayDate', label: 'Stay date (optional)', defaultValue: 'April 2026' },
],
Component: function BookingRatingGoogleEmail({ brand, data }) {
const guestName = data.guestName || 'Guest'
const bookingId = data.bookingId || ''
const googleReviewUrl = data.googleReviewUrl || '#'
const stayDate = data.stayDate || ''
const replyHref = `mailto:${brand.footer.email || 'reservations@example.com'}?subject=${encodeURI(
'Feedback for booking ' + bookingId
)}`
return (
<EmailShell
brand={brand}
title="How was your stay?"
previewText={`Hi ${guestName} — wed love your feedback for booking ${bookingId}.`}
>
<Heading style={{ fontSize: 22, margin: '0 0 8px', color: brand.textColor }}>
How was your stay?
</Heading>
<Text style={{ margin: '0 0 14px', color: brand.textColor, lineHeight: '24px' }}>
Hi {guestName}, thank you for staying with us at <strong>{brand.hotelName}</strong>.
{stayDate ? (
<>
{' '}Your stay: <strong>{stayDate}</strong>.
</>
) : null}
</Text>
<Section style={blockStyle(brand)}>
<Text style={{ margin: 0, color: brand.textColor, fontSize: 13, lineHeight: '20px' }}>
Please share your experience with a quick review. It only takes a moment.
</Text>
</Section>
<PrimaryCTA brand={brand} href={googleReviewUrl} label="Leave a Google review" />
<Text style={{ margin: '16px 0 0', color: brand.textColor, lineHeight: '22px' }}>
Prefer to share feedback directly? Reply to this email and our team will respond.
{' '}
<Link href={replyHref} style={{ color: brand.primaryColor }}>
Reply now
</Link>
</Text>
</EmailShell>
)
},
},
{
id: 'booking_rating_personal',
name: 'Booking Rating (Direct Reply)',
subjectTemplate: 'Help us improve: reply with your feedback, {{guestName}}',
ctaLabel: 'Reply to this email',
ctaHrefTemplate: 'mailto:{{contactEmail}}',
variables: [
{ key: 'guestName', label: 'Guest name', defaultValue: 'Daniel K.' },
{ key: 'bookingId', label: 'Booking ID', defaultValue: 'SH-8421' },
{ key: 'ratingScale', label: 'Rating scale text (optional)', defaultValue: '1 (low) to 5 (excellent)' },
],
Component: function BookingRatingPersonalEmail({ brand, data }) {
const guestName = data.guestName || 'Guest'
const bookingId = data.bookingId || ''
const ratingScale = data.ratingScale || ''
const href = `mailto:${brand.footer.email || 'reservations@example.com'}?subject=${encodeURI(
'Feedback for booking ' + bookingId
)}`
return (
<EmailShell
brand={brand}
title="Your feedback matters"
previewText={`Hi ${guestName} — reply with your feedback for booking ${bookingId}.`}
>
<Heading style={{ fontSize: 22, margin: '0 0 8px', color: brand.textColor }}>
Your feedback matters
</Heading>
<Text style={{ margin: '0 0 14px', color: brand.textColor, lineHeight: '24px' }}>
Hi {guestName}, were always improving. If you have a few seconds, please reply with how
your stay went for booking <strong>{bookingId}</strong>.
</Text>
<Section style={blockStyle(brand)}>
<Text style={{ margin: 0, color: brand.textColor, fontSize: 13, lineHeight: '20px' }}>
{ratingScale ? (
<>
Suggested scale: <strong>{ratingScale}</strong>.
</>
) : null}
{' '}Feel free to add any comments.
</Text>
</Section>
<PrimaryCTA brand={brand} href={href} label="Reply with feedback" />
<Text style={{ margin: '16px 0 0', color: brand.textColor, lineHeight: '22px' }}>
Thanks for choosing {brand.hotelName}.
</Text>
</EmailShell>
)
},
},
] ]
export { templates as templateRegistry } export { templates as templateRegistry }