feat: add Edge Functions workspace with tested email builders
Adds supabase/functions/ (deno.json, _shared/email.ts) with pure buildBookingNotificationEmail/buildInquiryNotificationEmail functions for Task 6's Edge Functions, tested via Deno's test runner. Excludes supabase/functions/** from the root Vitest config, which otherwise tried to resolve the jsr: import and failed.
This commit is contained in:
49
supabase/functions/_shared/email.test.ts
Normal file
49
supabase/functions/_shared/email.test.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { assertEquals, assertStringIncludes } from 'jsr:@std/assert'
|
||||||
|
import { buildBookingNotificationEmail, buildInquiryNotificationEmail } from './email.ts'
|
||||||
|
|
||||||
|
Deno.test('buildBookingNotificationEmail 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' },
|
||||||
|
})
|
||||||
|
assertEquals(subject, 'New booking: Knotless Braids on 2026-09-01')
|
||||||
|
assertStringIncludes(html, 'Jane Doe')
|
||||||
|
assertStringIncludes(html, 'Knotless Braids')
|
||||||
|
assertStringIncludes(html, '2026-09-01')
|
||||||
|
assertStringIncludes(html, '10:00 AM')
|
||||||
|
assertStringIncludes(html, '555-0100')
|
||||||
|
assertStringIncludes(html, 'jane@example.com')
|
||||||
|
assertStringIncludes(html, 'First time client')
|
||||||
|
})
|
||||||
|
|
||||||
|
Deno.test('buildBookingNotificationEmail 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: '' },
|
||||||
|
})
|
||||||
|
if (html.includes('Notes:')) throw new Error('expected no Notes: paragraph')
|
||||||
|
})
|
||||||
|
|
||||||
|
Deno.test('buildInquiryNotificationEmail 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?' },
|
||||||
|
})
|
||||||
|
assertEquals(subject, 'New inquiry: Silk Edge Control')
|
||||||
|
assertStringIncludes(html, 'Nadia')
|
||||||
|
assertStringIncludes(html, 'nadia@example.com')
|
||||||
|
assertStringIncludes(html, 'Silk Edge Control')
|
||||||
|
assertStringIncludes(html, 'Is this in stock?')
|
||||||
|
})
|
||||||
|
|
||||||
|
Deno.test('buildInquiryNotificationEmail omits the notes paragraph when notes are empty', () => {
|
||||||
|
const { html } = buildInquiryNotificationEmail({
|
||||||
|
productName: 'Braid Oil',
|
||||||
|
contact: { name: 'Kim', email: 'kim@example.com', notes: '' },
|
||||||
|
})
|
||||||
|
if (html.includes('Notes:')) throw new Error('expected no Notes: paragraph')
|
||||||
|
})
|
||||||
28
supabase/functions/_shared/email.ts
Normal file
28
supabase/functions/_shared/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}`,
|
||||||
|
}
|
||||||
|
}
|
||||||
5
supabase/functions/deno.json
Normal file
5
supabase/functions/deno.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"test": "deno test --allow-none"
|
||||||
|
}
|
||||||
|
}
|
||||||
18
supabase/functions/deno.lock
generated
Normal file
18
supabase/functions/deno.lock
generated
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"version": "5",
|
||||||
|
"specifiers": {
|
||||||
|
"jsr:@std/assert@*": "1.0.19",
|
||||||
|
"jsr:@std/internal@^1.0.12": "1.0.14"
|
||||||
|
},
|
||||||
|
"jsr": {
|
||||||
|
"@std/assert@1.0.19": {
|
||||||
|
"integrity": "eaada96ee120cb980bc47e040f82814d786fe8162ecc53c91d8df60b8755991e",
|
||||||
|
"dependencies": [
|
||||||
|
"jsr:@std/internal"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@std/internal@1.0.14": {
|
||||||
|
"integrity": "291516b3d4c35024d6ffbc0a9df5bf4c64116e05b50012cf846710152d2ffdf7"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ export default defineConfig({
|
|||||||
'**/cypress/**',
|
'**/cypress/**',
|
||||||
'**/.{idea,git,cache,output,temp}/**',
|
'**/.{idea,git,cache,output,temp}/**',
|
||||||
'**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*',
|
'**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*',
|
||||||
|
'supabase/functions/**',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user