feat: lunch reminder + recap email templates
Adds sendLunchReminderEmail and sendLunchRecapEmail. Templates use Intl.DateTimeFormat with Europe/Monaco zone. Reuses existing escapeHtml helper. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -79,7 +79,7 @@ describe('dish CRUD', () => {
|
||||
lunchEventId: event.id, name: 'Risotto', dietaryTags: ['VEGETARIAN'], sortOrder: 0,
|
||||
})
|
||||
const dishes = await caller.listDishes({ lunchEventId: event.id })
|
||||
expect(dishes.map((d) => d.name)).toEqual(['Risotto', 'Sea bass'])
|
||||
expect(dishes.map((d: { name: string }) => d.name)).toEqual(['Risotto', 'Sea bass'])
|
||||
})
|
||||
|
||||
it('updateDish patches name + tags', async () => {
|
||||
@@ -144,7 +144,7 @@ describe('dish CRUD', () => {
|
||||
],
|
||||
})
|
||||
const dishes = await caller.listDishes({ lunchEventId: event.id })
|
||||
expect(dishes.map((d) => d.name)).toEqual(['b', 'a'])
|
||||
expect(dishes.map((d: { name: string }) => d.name)).toEqual(['b', 'a'])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -168,8 +168,8 @@ describe('external attendees CRUD', () => {
|
||||
})
|
||||
const list = await caller.listExternals({ lunchEventId: event.id })
|
||||
expect(list).toHaveLength(2)
|
||||
expect(list.find((e) => e.name === 'Princess Albert')?.projectId).toBeNull()
|
||||
expect(list.find((e) => e.name === 'Speaker Smith')?.projectId).toBe(project.id)
|
||||
expect(list.find((e: { name: string }) => e.name === 'Princess Albert')?.projectId).toBeNull()
|
||||
expect(list.find((e: { name: string }) => e.name === 'Speaker Smith')?.projectId).toBe(project.id)
|
||||
})
|
||||
|
||||
it('updateExternal patches fields including dishId + allergens', async () => {
|
||||
@@ -197,7 +197,7 @@ describe('external attendees CRUD', () => {
|
||||
const ext = await caller.createExternal({ lunchEventId: event.id, name: 'tmp' })
|
||||
await caller.deleteExternal({ externalId: ext.id })
|
||||
const list = await caller.listExternals({ lunchEventId: event.id })
|
||||
expect(list.find((e) => e.id === ext.id)).toBeUndefined()
|
||||
expect(list.find((e: { id: string }) => e.id === ext.id)).toBeUndefined()
|
||||
})
|
||||
|
||||
it('rejects non-admin createExternal', async () => {
|
||||
@@ -308,8 +308,8 @@ describe('lunch.getTeamPicks', () => {
|
||||
})
|
||||
const picks = await caller.getTeamPicks({ projectId: project.id })
|
||||
expect(picks).toHaveLength(2)
|
||||
expect(picks.find((p) => p.userId === m1.id)?.hasPicked).toBe(true)
|
||||
expect(picks.find((p) => p.userId === m2.id)?.hasPicked).toBe(false)
|
||||
expect(picks.find((p: { userId: string }) => p.userId === m1.id)?.hasPicked).toBe(true)
|
||||
expect(picks.find((p: { userId: string }) => p.userId === m2.id)?.hasPicked).toBe(false)
|
||||
})
|
||||
|
||||
it('rejects non-team-member callers', async () => {
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
uid,
|
||||
} from '../helpers'
|
||||
import { lunchRouter } from '@/server/routers/lunch'
|
||||
import type { UserRole } from '@prisma/client'
|
||||
|
||||
const programIds: string[] = []
|
||||
const userIds: string[] = []
|
||||
@@ -77,7 +78,7 @@ async function setupTeam(opts: {
|
||||
return { program, lead, member, admin, project, attendingMember: am, dish, event }
|
||||
}
|
||||
|
||||
function callerFor(user: { id: string; email: string; role: string }) {
|
||||
function callerFor(user: { id: string; email: string; role: UserRole }) {
|
||||
return createCaller(lunchRouter, user)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user