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:
Matt
2026-04-28 14:29:42 +02:00
parent 16156111a6
commit b867c45114
5 changed files with 465 additions and 9 deletions

View File

@@ -75,7 +75,9 @@ import {
Eye,
Plus,
X,
Mail,
} from 'lucide-react'
import { ProjectEmailDialog } from '@/components/admin/project-email-dialog'
import { toast } from 'sonner'
import { formatDateOnly } from '@/lib/utils'
import { getCountryName, getCountryFlag } from '@/lib/countries'
@@ -161,6 +163,7 @@ function ProjectDetailContent({ projectId }: { projectId: string }) {
// State for remove member confirmation
const [removingMemberId, setRemovingMemberId] = useState<string | null>(null)
const [emailDialogOpen, setEmailDialogOpen] = useState(false)
const addTeamMember = trpc.project.addTeamMember.useMutation({
onSuccess: () => {
@@ -269,14 +272,29 @@ function ProjectDetailContent({ projectId }: { projectId: string }) {
</div>
</div>
<Button variant="outline" asChild>
<Link href={`/admin/projects/${projectId}/edit`}>
<Edit className="mr-2 h-4 w-4" />
Edit
</Link>
</Button>
<div className="flex items-center gap-2">
<Button variant="outline" onClick={() => setEmailDialogOpen(true)}>
<Mail className="mr-2 h-4 w-4" />
Email Team
</Button>
<Button variant="outline" asChild>
<Link href={`/admin/projects/${projectId}/edit`}>
<Edit className="mr-2 h-4 w-4" />
Edit
</Link>
</Button>
</div>
</div>
{project && (
<ProjectEmailDialog
open={emailDialogOpen}
onClose={() => setEmailDialogOpen(false)}
projectId={project.id}
projectTitle={project.title}
/>
)}
<Separator />
{/* Stats Grid */}