/**
* Email Digest Template
* React Email template for daily/weekly digests
*/
import * as React from 'react';
import {
Html,
Head,
Body,
Container,
Section,
Text,
Link,
Hr,
Preview,
} from '@react-email/components';
export interface DigestPost {
id: string;
title: string;
url: string;
author: string;
upvotes: number;
downvotes: number;
replies: number;
excerpt?: string;
category?: string;
}
export interface DigestBill {
id: string;
number: string;
title: string;
url: string;
sponsor?: string;
status?: string;
}
interface DigestEmailProps {
userName: string;
digestType: 'daily' | 'weekly';
trendingPosts: DigestPost[];
controversialPosts: DigestPost[];
newBills: DigestBill[];
unsubscribeUrl: string;
preferencesUrl: string;
}
export function DigestEmailTemplate({
userName,
digestType,
trendingPosts,
controversialPosts,
newBills,
unsubscribeUrl,
preferencesUrl,
}: DigestEmailProps) {
const previewText = `Your ${digestType} parliamentary digest from CanadaGPT`;
return (
<Html>
<Head />
<Preview>{previewText}</Preview>
<Body style={styles.body}>
<Container style={styles.container}>
{/* Header */}
<Section style={styles.header}>
<Text style={styles.logo}>π CanadaGPT</Text>
<Text style={styles.title}>
Your {digestType === 'daily' ? 'Daily' : 'Weekly'} Parliamentary Digest
</Text>
</Section>
{/* Greeting */}
<Section style={styles.section}>
<Text style={styles.greeting}>Hi {userName},</Text>
<Text style={styles.intro}>
Here's what's been happening in Canadian Parliament:
</Text>
</Section>
{/* Trending Posts */}
{trendingPosts.length > 0 && (
<Section style={styles.section}>
<Text style={styles.sectionTitle}>π₯ Trending Discussions</Text>
{trendingPosts.map((post) => (
<div key={post.id} style={styles.postCard}>
<Link href={post.url} style={styles.postTitle}>
{post.title}
</Link>
{post.excerpt && (
<Text style={styles.postExcerpt}>{post.excerpt}...</Text>
)}
<Text style={styles.postMeta}>
by {post.author} β’ {post.upvotes} upvotes β’ {post.replies} replies
{post.category && ` β’ ${post.category}`}
</Text>
</div>
))}
</Section>
)}
{/* Controversial Posts */}
{controversialPosts.length > 0 && (
<Section style={styles.section}>
<Text style={styles.sectionTitle}>β‘ Hot Debates</Text>
{controversialPosts.map((post) => (
<div key={post.id} style={styles.postCard}>
<Link href={post.url} style={styles.postTitle}>
{post.title}
</Link>
<Text style={styles.postMeta}>
by {post.author} β’ {post.upvotes}β {post.downvotes}β β’ {post.replies} replies
</Text>
</div>
))}
</Section>
)}
{/* New Bills */}
{newBills.length > 0 && (
<Section style={styles.section}>
<Text style={styles.sectionTitle}>π New Legislation</Text>
{newBills.map((bill) => (
<div key={bill.id} style={styles.postCard}>
<Link href={bill.url} style={styles.postTitle}>
{bill.number}: {bill.title}
</Link>
<Text style={styles.postMeta}>
{bill.sponsor && `Sponsored by ${bill.sponsor}`}
{bill.status && ` β’ ${bill.status}`}
</Text>
</div>
))}
</Section>
)}
<Hr style={styles.hr} />
{/* Footer */}
<Section style={styles.footer}>
<Text style={styles.footerText}>
<Link href={preferencesUrl} style={styles.footerLink}>
Manage preferences
</Link>
{' β’ '}
<Link href={unsubscribeUrl} style={styles.footerLink}>
Unsubscribe
</Link>
</Text>
<Text style={styles.footerText}>
CanadaGPT - Making government accountability accessible
</Text>
</Section>
</Container>
</Body>
</Html>
);
}
/**
* Generate plain text version of digest
*/
export function getPlainTextDigest({
userName,
digestType,
trendingPosts,
controversialPosts,
newBills,
}: Omit<DigestEmailProps, 'unsubscribeUrl' | 'preferencesUrl'>): string {
let text = `Hi ${userName},\n\n`;
text += `Here's your ${digestType} parliamentary digest from CanadaGPT:\n\n`;
if (trendingPosts.length > 0) {
text += `π₯ TRENDING DISCUSSIONS\n`;
text += `${'='.repeat(30)}\n\n`;
for (const post of trendingPosts) {
text += `${post.title}\n`;
text += `by ${post.author} β’ ${post.upvotes} upvotes β’ ${post.replies} replies\n`;
text += `${post.url}\n\n`;
}
}
if (controversialPosts.length > 0) {
text += `β‘ HOT DEBATES\n`;
text += `${'='.repeat(30)}\n\n`;
for (const post of controversialPosts) {
text += `${post.title}\n`;
text += `by ${post.author} β’ ${post.upvotes}β ${post.downvotes}β\n`;
text += `${post.url}\n\n`;
}
}
if (newBills.length > 0) {
text += `π NEW LEGISLATION\n`;
text += `${'='.repeat(30)}\n\n`;
for (const bill of newBills) {
text += `${bill.number}: ${bill.title}\n`;
if (bill.sponsor) text += `Sponsored by ${bill.sponsor}\n`;
text += `${bill.url}\n\n`;
}
}
text += `\n---\n`;
text += `Manage your preferences: https://canadagpt.ca/en/settings/notifications\n`;
text += `CanadaGPT - Making government accountability accessible\n`;
return text;
}
const styles = {
body: {
backgroundColor: '#f4f4f5',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
},
container: {
margin: '0 auto',
padding: '20px 0 48px',
maxWidth: '600px',
},
header: {
backgroundColor: '#dc2626',
padding: '32px 24px',
borderRadius: '8px 8px 0 0',
},
logo: {
color: '#ffffff',
fontSize: '24px',
fontWeight: 'bold',
margin: '0 0 8px 0',
},
title: {
color: '#ffffff',
fontSize: '18px',
margin: '0',
},
section: {
backgroundColor: '#ffffff',
padding: '24px',
},
greeting: {
fontSize: '16px',
color: '#18181b',
margin: '0 0 8px 0',
},
intro: {
fontSize: '14px',
color: '#52525b',
margin: '0',
},
sectionTitle: {
fontSize: '16px',
fontWeight: 'bold',
color: '#18181b',
margin: '0 0 16px 0',
},
postCard: {
backgroundColor: '#f4f4f5',
padding: '16px',
borderRadius: '8px',
marginBottom: '12px',
},
postTitle: {
fontSize: '14px',
fontWeight: '600',
color: '#dc2626',
textDecoration: 'none',
},
postExcerpt: {
fontSize: '13px',
color: '#52525b',
margin: '8px 0 0 0',
lineHeight: '1.4',
},
postMeta: {
fontSize: '12px',
color: '#71717a',
margin: '8px 0 0 0',
},
hr: {
borderColor: '#e4e4e7',
margin: '0',
},
footer: {
backgroundColor: '#ffffff',
padding: '24px',
borderRadius: '0 0 8px 8px',
textAlign: 'center' as const,
},
footerText: {
fontSize: '12px',
color: '#71717a',
margin: '0 0 8px 0',
},
footerLink: {
color: '#dc2626',
textDecoration: 'none',
},
};