feat: add Cloud Functions scaffold with tested email builders
This commit is contained in:
53
functions/src/email.test.ts
Normal file
53
functions/src/email.test.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { buildBookingNotificationEmail, buildInquiryNotificationEmail } from './email.js'
|
||||
|
||||
describe('buildBookingNotificationEmail', () => {
|
||||
it('includes the service, date, time, and contact details', () => {
|
||||
const { subject, html } = buildBookingNotificationEmail({
|
||||
serviceName: 'Knotless Braids',
|
||||
date: '2026-09-01',
|
||||
time: '10:00 AM',
|
||||
contact: { name: 'Jane Doe', phone: '555-0100', email: 'jane@example.com', notes: 'First time client' },
|
||||
})
|
||||
expect(subject).toBe('New booking: Knotless Braids on 2026-09-01')
|
||||
expect(html).toContain('Jane Doe')
|
||||
expect(html).toContain('Knotless Braids')
|
||||
expect(html).toContain('2026-09-01')
|
||||
expect(html).toContain('10:00 AM')
|
||||
expect(html).toContain('555-0100')
|
||||
expect(html).toContain('jane@example.com')
|
||||
expect(html).toContain('First time client')
|
||||
})
|
||||
|
||||
it('omits the notes paragraph when notes are empty', () => {
|
||||
const { html } = buildBookingNotificationEmail({
|
||||
serviceName: 'Cornrows',
|
||||
date: '2026-09-02',
|
||||
time: '1:00 PM',
|
||||
contact: { name: 'Amy', phone: '555-0101', email: 'amy@example.com', notes: '' },
|
||||
})
|
||||
expect(html).not.toContain('Notes:')
|
||||
})
|
||||
})
|
||||
|
||||
describe('buildInquiryNotificationEmail', () => {
|
||||
it('includes the product and contact details', () => {
|
||||
const { subject, html } = buildInquiryNotificationEmail({
|
||||
productName: 'Silk Edge Control',
|
||||
contact: { name: 'Nadia', email: 'nadia@example.com', notes: 'Is this in stock?' },
|
||||
})
|
||||
expect(subject).toBe('New inquiry: Silk Edge Control')
|
||||
expect(html).toContain('Nadia')
|
||||
expect(html).toContain('nadia@example.com')
|
||||
expect(html).toContain('Silk Edge Control')
|
||||
expect(html).toContain('Is this in stock?')
|
||||
})
|
||||
|
||||
it('omits the notes paragraph when notes are empty', () => {
|
||||
const { html } = buildInquiryNotificationEmail({
|
||||
productName: 'Braid Oil',
|
||||
contact: { name: 'Kim', email: 'kim@example.com', notes: '' },
|
||||
})
|
||||
expect(html).not.toContain('Notes:')
|
||||
})
|
||||
})
|
||||
28
functions/src/email.ts
Normal file
28
functions/src/email.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
export interface BookingEmailInput {
|
||||
serviceName: string
|
||||
date: string
|
||||
time: string
|
||||
contact: { name: string; phone: string; email: string; notes: string }
|
||||
}
|
||||
|
||||
export function buildBookingNotificationEmail(input: BookingEmailInput): { subject: string; html: string } {
|
||||
const notesLine = input.contact.notes ? `<p>Notes: ${input.contact.notes}</p>` : ''
|
||||
return {
|
||||
subject: `New booking: ${input.serviceName} on ${input.date}`,
|
||||
html: `<p><strong>${input.contact.name}</strong> booked <strong>${input.serviceName}</strong> on ${input.date} at ${input.time}.</p>` +
|
||||
`<p>Phone: ${input.contact.phone}<br/>Email: ${input.contact.email}</p>${notesLine}`,
|
||||
}
|
||||
}
|
||||
|
||||
export interface InquiryEmailInput {
|
||||
productName: string
|
||||
contact: { name: string; email: string; notes: string }
|
||||
}
|
||||
|
||||
export function buildInquiryNotificationEmail(input: InquiryEmailInput): { subject: string; html: string } {
|
||||
const notesLine = input.contact.notes ? `<p>Notes: ${input.contact.notes}</p>` : ''
|
||||
return {
|
||||
subject: `New inquiry: ${input.productName}`,
|
||||
html: `<p><strong>${input.contact.name}</strong> (${input.contact.email}) asked about <strong>${input.productName}</strong>.</p>${notesLine}`,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user