feat: Email Team button + custom-email dialog on project page
Adds a PROJECT_TEAM recipient type to the message router (resolver returns team members + project lead) and an "Email Team" button on the admin project detail page that opens a self-contained dialog matching the look of /admin/messages: subject, body (pre-filled with "Hello [Project Title] team,\n\n"), live HTML preview iframe, "Send test to me" + "Send to N" actions. The composer reuses the existing message.previewEmail and message.send tRPC procedures end-to-end — no parallel email infrastructure introduced. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
60
tests/unit/message-recipient-project-team.test.ts
Normal file
60
tests/unit/message-recipient-project-team.test.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
||||
import { prisma, createCaller } from '../setup'
|
||||
import {
|
||||
createTestUser, createTestProgram, createTestProject, cleanupTestData, uid,
|
||||
} from '../helpers'
|
||||
import { messageRouter } from '../../src/server/routers/message'
|
||||
|
||||
describe('message.previewRecipients — PROJECT_TEAM', () => {
|
||||
let programId: string
|
||||
let admin: { id: string; email: string; role: 'SUPER_ADMIN' }
|
||||
let projectId: string
|
||||
const userIds: string[] = []
|
||||
|
||||
beforeAll(async () => {
|
||||
const program = await createTestProgram({ name: `proj-team-${uid()}` })
|
||||
programId = program.id
|
||||
|
||||
const lead = await createTestUser('APPLICANT')
|
||||
userIds.push(lead.id)
|
||||
const project = await createTestProject(programId, { title: 'TestProj' })
|
||||
projectId = project.id
|
||||
await prisma.project.update({ where: { id: projectId }, data: { submittedByUserId: lead.id } })
|
||||
|
||||
const member1 = await createTestUser('APPLICANT')
|
||||
const member2 = await createTestUser('APPLICANT')
|
||||
userIds.push(member1.id, member2.id)
|
||||
await prisma.teamMember.createMany({
|
||||
data: [
|
||||
{ id: uid('tm'), projectId, userId: member1.id, role: 'MEMBER' },
|
||||
{ id: uid('tm'), projectId, userId: member2.id, role: 'MEMBER' },
|
||||
],
|
||||
})
|
||||
|
||||
const a = await createTestUser('SUPER_ADMIN')
|
||||
userIds.push(a.id)
|
||||
admin = { id: a.id, email: a.email, role: 'SUPER_ADMIN' }
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanupTestData(programId, userIds)
|
||||
})
|
||||
|
||||
it('counts the lead + 2 team members', async () => {
|
||||
const caller = createCaller(messageRouter, admin)
|
||||
const result = await caller.previewRecipients({
|
||||
recipientType: 'PROJECT_TEAM',
|
||||
recipientFilter: { projectId },
|
||||
})
|
||||
expect(result.totalApplicants).toBe(3)
|
||||
})
|
||||
|
||||
it('returns 0 when projectId is missing', async () => {
|
||||
const caller = createCaller(messageRouter, admin)
|
||||
const result = await caller.previewRecipients({
|
||||
recipientType: 'PROJECT_TEAM',
|
||||
recipientFilter: {},
|
||||
})
|
||||
expect(result.totalApplicants).toBe(0)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user