first
This commit is contained in:
parent
169f5082c0
commit
ecf7b549a3
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
node_modules
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
.DS_Store
|
||||||
|
*.log
|
||||||
|
.env.local
|
||||||
|
.env*.local
|
||||||
36
app/api/email/[template]/route.ts
Normal file
36
app/api/email/[template]/route.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { render } from '@react-email/render';
|
||||||
|
import { WelcomeEmail } from '../../../../emails/welcome';
|
||||||
|
import { TransactionCompleteEmail } from '../../../../emails/transaction-complete';
|
||||||
|
import { EventTicketEmail } from '../../../../emails/event-ticket';
|
||||||
|
import { PaymentRequestEmail } from '../../../../emails/payment-request';
|
||||||
|
import { ReferralSuccessEmail } from '../../../../emails/referral-success';
|
||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const templates: Record<string, React.ComponentType<any>> = {
|
||||||
|
welcome: WelcomeEmail,
|
||||||
|
'transaction-complete': TransactionCompleteEmail,
|
||||||
|
'event-ticket': EventTicketEmail,
|
||||||
|
'payment-request': PaymentRequestEmail,
|
||||||
|
'referral-success': ReferralSuccessEmail,
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function GET(
|
||||||
|
request: Request,
|
||||||
|
context: { params: Promise<{ template: string }> }
|
||||||
|
) {
|
||||||
|
const { template } = await context.params;
|
||||||
|
const TemplateComponent = templates[template];
|
||||||
|
|
||||||
|
if (!TemplateComponent) {
|
||||||
|
return NextResponse.json({ error: 'Template not found' }, { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const html = render(React.createElement(TemplateComponent));
|
||||||
|
|
||||||
|
return new NextResponse(html, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/html',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
18
app/layout.tsx
Normal file
18
app/layout.tsx
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Amba Email Templates',
|
||||||
|
description: 'Preview and test email templates for Amba app',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<body>{children}</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
270
app/page.tsx
Normal file
270
app/page.tsx
Normal file
|
|
@ -0,0 +1,270 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { theme } from '../emails/theme';
|
||||||
|
|
||||||
|
const templates = [
|
||||||
|
{
|
||||||
|
id: 'welcome',
|
||||||
|
name: 'Welcome Email',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'transaction-complete',
|
||||||
|
name: 'Transaction Complete',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'event-ticket',
|
||||||
|
name: 'Event Ticket',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'payment-request',
|
||||||
|
name: 'Payment Request',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'referral-success',
|
||||||
|
name: 'Referral Success',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
const [selectedTemplate, setSelectedTemplate] = useState(templates[0]);
|
||||||
|
const [htmlContent, setHtmlContent] = useState<string>('');
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
const loadTemplate = async (templateId: string) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/email/${templateId}`);
|
||||||
|
const html = await response.text();
|
||||||
|
setHtmlContent(html);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading template:', error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTemplateChange = (template: typeof templates[0]) => {
|
||||||
|
setSelectedTemplate(template);
|
||||||
|
loadTemplate(template.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDownload = () => {
|
||||||
|
const blob = new Blob([htmlContent], { type: 'text/html' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = `${selectedTemplate.id}.html`;
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load initial template
|
||||||
|
useEffect(() => {
|
||||||
|
loadTemplate(selectedTemplate.id);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={container}>
|
||||||
|
<header style={header}>
|
||||||
|
<h1 style={title}>Amba Email Templates</h1>
|
||||||
|
<p style={subtitle}>Preview and test all email templates</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div style={content}>
|
||||||
|
<aside style={sidebar}>
|
||||||
|
<h2 style={sidebarTitle}>Templates</h2>
|
||||||
|
<nav style={nav}>
|
||||||
|
{templates.map((template) => (
|
||||||
|
<button
|
||||||
|
key={template.id}
|
||||||
|
onClick={() => handleTemplateChange(template)}
|
||||||
|
style={{
|
||||||
|
...navButton,
|
||||||
|
...(selectedTemplate.id === template.id
|
||||||
|
? activeNavButton
|
||||||
|
: {}),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{template.name}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<main style={previewSection}>
|
||||||
|
<div style={previewHeader}>
|
||||||
|
<h2 style={previewTitle}>{selectedTemplate.name}</h2>
|
||||||
|
<button
|
||||||
|
onClick={handleDownload}
|
||||||
|
disabled={!htmlContent}
|
||||||
|
style={{
|
||||||
|
...downloadButton,
|
||||||
|
...(loading ? { opacity: 0.5, cursor: 'not-allowed' } : {}),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Download HTML
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div style={emailContainer}>
|
||||||
|
{loading ? (
|
||||||
|
<div style={loadingContainer}>
|
||||||
|
<p>Loading template...</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<iframe
|
||||||
|
srcDoc={htmlContent}
|
||||||
|
style={iframe}
|
||||||
|
title={selectedTemplate.name}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const container = {
|
||||||
|
minHeight: '100vh',
|
||||||
|
backgroundColor: '#f5f5f5',
|
||||||
|
fontFamily: theme.fonts.sans,
|
||||||
|
};
|
||||||
|
|
||||||
|
const header = {
|
||||||
|
backgroundColor: theme.colors.primary,
|
||||||
|
color: theme.colors.background,
|
||||||
|
padding: theme.spacing.lg,
|
||||||
|
textAlign: 'center' as const,
|
||||||
|
};
|
||||||
|
|
||||||
|
const title = {
|
||||||
|
margin: '0 0 8px',
|
||||||
|
fontSize: '32px',
|
||||||
|
fontWeight: '700',
|
||||||
|
};
|
||||||
|
|
||||||
|
const subtitle = {
|
||||||
|
margin: '0',
|
||||||
|
fontSize: '16px',
|
||||||
|
opacity: 0.9,
|
||||||
|
};
|
||||||
|
|
||||||
|
const content = {
|
||||||
|
display: 'flex',
|
||||||
|
maxWidth: '1400px',
|
||||||
|
margin: '0 auto',
|
||||||
|
padding: theme.spacing.md,
|
||||||
|
gap: theme.spacing.md,
|
||||||
|
};
|
||||||
|
|
||||||
|
const sidebar = {
|
||||||
|
width: '250px',
|
||||||
|
backgroundColor: theme.colors.background,
|
||||||
|
borderRadius: '8px',
|
||||||
|
padding: theme.spacing.md,
|
||||||
|
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
|
||||||
|
height: 'fit-content',
|
||||||
|
position: 'sticky' as const,
|
||||||
|
top: theme.spacing.md,
|
||||||
|
};
|
||||||
|
|
||||||
|
const sidebarTitle = {
|
||||||
|
margin: '0 0 16px',
|
||||||
|
fontSize: '18px',
|
||||||
|
fontWeight: '600',
|
||||||
|
color: theme.colors.primary,
|
||||||
|
};
|
||||||
|
|
||||||
|
const nav = {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column' as const,
|
||||||
|
gap: '8px',
|
||||||
|
};
|
||||||
|
|
||||||
|
const navButton = {
|
||||||
|
padding: '12px 16px',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '6px',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
color: theme.colors.textDark,
|
||||||
|
fontSize: '14px',
|
||||||
|
fontWeight: '500',
|
||||||
|
cursor: 'pointer',
|
||||||
|
textAlign: 'left' as const,
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
};
|
||||||
|
|
||||||
|
const activeNavButton = {
|
||||||
|
backgroundColor: theme.colors.accentLight,
|
||||||
|
color: theme.colors.primary,
|
||||||
|
fontWeight: '600',
|
||||||
|
};
|
||||||
|
|
||||||
|
const previewSection = {
|
||||||
|
flex: '1',
|
||||||
|
backgroundColor: theme.colors.background,
|
||||||
|
borderRadius: '8px',
|
||||||
|
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column' as const,
|
||||||
|
};
|
||||||
|
|
||||||
|
const previewHeader = {
|
||||||
|
padding: theme.spacing.md,
|
||||||
|
borderBottom: `1px solid ${theme.colors.border}`,
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
};
|
||||||
|
|
||||||
|
const previewTitle = {
|
||||||
|
margin: '0',
|
||||||
|
fontSize: '24px',
|
||||||
|
fontWeight: '600',
|
||||||
|
color: theme.colors.primary,
|
||||||
|
};
|
||||||
|
|
||||||
|
const downloadButton = {
|
||||||
|
padding: '8px 16px',
|
||||||
|
backgroundColor: theme.colors.primary,
|
||||||
|
color: theme.colors.background,
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '6px',
|
||||||
|
fontSize: '14px',
|
||||||
|
fontWeight: '600',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'background-color 0.2s',
|
||||||
|
};
|
||||||
|
|
||||||
|
const emailContainer = {
|
||||||
|
flex: '1',
|
||||||
|
padding: theme.spacing.lg,
|
||||||
|
overflow: 'auto',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
backgroundColor: '#e0e0e0',
|
||||||
|
};
|
||||||
|
|
||||||
|
const iframe = {
|
||||||
|
width: '100%',
|
||||||
|
maxWidth: '600px',
|
||||||
|
height: '800px',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '8px',
|
||||||
|
backgroundColor: theme.colors.background,
|
||||||
|
boxShadow: '0 4px 8px rgba(0,0,0,0.1)',
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadingContainer = {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
width: '100%',
|
||||||
|
maxWidth: '600px',
|
||||||
|
height: '800px',
|
||||||
|
backgroundColor: theme.colors.background,
|
||||||
|
borderRadius: '8px',
|
||||||
|
color: theme.colors.textDark,
|
||||||
|
fontSize: '16px',
|
||||||
|
};
|
||||||
28
emails/components/Button.tsx
Normal file
28
emails/components/Button.tsx
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { Button as ReactEmailButton } from '@react-email/components';
|
||||||
|
import { theme } from '../theme';
|
||||||
|
|
||||||
|
interface ButtonProps {
|
||||||
|
href: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Button = ({ href, children }: ButtonProps) => {
|
||||||
|
return (
|
||||||
|
<ReactEmailButton
|
||||||
|
href={href}
|
||||||
|
style={{
|
||||||
|
backgroundColor: theme.colors.primary,
|
||||||
|
borderRadius: '8px',
|
||||||
|
color: theme.colors.background,
|
||||||
|
fontSize: '16px',
|
||||||
|
fontWeight: '600',
|
||||||
|
textDecoration: 'none',
|
||||||
|
textAlign: 'center' as const,
|
||||||
|
display: 'inline-block',
|
||||||
|
padding: '12px 24px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ReactEmailButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
21
emails/components/Card.tsx
Normal file
21
emails/components/Card.tsx
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { Section, Text } from '@react-email/components';
|
||||||
|
import { theme } from '../theme';
|
||||||
|
|
||||||
|
interface CardProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Card = ({ children }: CardProps) => {
|
||||||
|
return (
|
||||||
|
<Section
|
||||||
|
style={{
|
||||||
|
backgroundColor: theme.colors.accentLight,
|
||||||
|
borderRadius: '8px',
|
||||||
|
padding: theme.spacing.md,
|
||||||
|
margin: `${theme.spacing.md} 0`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Section>
|
||||||
|
);
|
||||||
|
};
|
||||||
101
emails/components/EmailLayout.tsx
Normal file
101
emails/components/EmailLayout.tsx
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Container,
|
||||||
|
Head,
|
||||||
|
Heading,
|
||||||
|
Html,
|
||||||
|
Img,
|
||||||
|
Link,
|
||||||
|
Preview,
|
||||||
|
Section,
|
||||||
|
Text,
|
||||||
|
} from '@react-email/components';
|
||||||
|
import { theme } from '../theme';
|
||||||
|
import { logoDataUri } from '../utils/logoDataUri';
|
||||||
|
|
||||||
|
interface EmailLayoutProps {
|
||||||
|
preview?: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
logoUrl?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EmailLayout = ({
|
||||||
|
preview,
|
||||||
|
children,
|
||||||
|
logoUrl = logoDataUri,
|
||||||
|
}: EmailLayoutProps) => {
|
||||||
|
return (
|
||||||
|
<Html>
|
||||||
|
<Head />
|
||||||
|
<Preview>{preview}</Preview>
|
||||||
|
<Body style={main}>
|
||||||
|
<Container style={container}>
|
||||||
|
<Section style={header}>
|
||||||
|
<Img
|
||||||
|
src={logoUrl}
|
||||||
|
width="86"
|
||||||
|
height="80"
|
||||||
|
alt="Amba Logo"
|
||||||
|
style={logo}
|
||||||
|
/>
|
||||||
|
</Section>
|
||||||
|
{children}
|
||||||
|
<Section style={footer}>
|
||||||
|
<Text style={footerText}>
|
||||||
|
© {new Date().getFullYear()} Amba. All rights reserved.
|
||||||
|
</Text>
|
||||||
|
<Text style={footerText}>
|
||||||
|
<Link href="#" style={footerLink}>
|
||||||
|
Privacy Policy
|
||||||
|
</Link>{' '}
|
||||||
|
|{' '}
|
||||||
|
<Link href="#" style={footerLink}>
|
||||||
|
Terms of Service
|
||||||
|
</Link>
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
</Container>
|
||||||
|
</Body>
|
||||||
|
</Html>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const main = {
|
||||||
|
backgroundColor: theme.colors.background,
|
||||||
|
fontFamily: theme.fonts.sans,
|
||||||
|
};
|
||||||
|
|
||||||
|
const container = {
|
||||||
|
margin: '0 auto',
|
||||||
|
padding: '20px 0 48px',
|
||||||
|
maxWidth: '600px',
|
||||||
|
};
|
||||||
|
|
||||||
|
const header = {
|
||||||
|
padding: '32px 0',
|
||||||
|
textAlign: 'center' as const,
|
||||||
|
borderBottom: `2px solid ${theme.colors.accent}`,
|
||||||
|
marginBottom: '32px',
|
||||||
|
};
|
||||||
|
|
||||||
|
const logo = {
|
||||||
|
margin: '0 auto',
|
||||||
|
};
|
||||||
|
|
||||||
|
const footer = {
|
||||||
|
marginTop: '48px',
|
||||||
|
paddingTop: '24px',
|
||||||
|
borderTop: `1px solid ${theme.colors.border}`,
|
||||||
|
textAlign: 'center' as const,
|
||||||
|
};
|
||||||
|
|
||||||
|
const footerText = {
|
||||||
|
fontSize: '12px',
|
||||||
|
color: theme.colors.text,
|
||||||
|
margin: '4px 0',
|
||||||
|
};
|
||||||
|
|
||||||
|
const footerLink = {
|
||||||
|
color: theme.colors.primary,
|
||||||
|
textDecoration: 'underline',
|
||||||
|
};
|
||||||
222
emails/event-ticket.tsx
Normal file
222
emails/event-ticket.tsx
Normal file
|
|
@ -0,0 +1,222 @@
|
||||||
|
import {
|
||||||
|
Heading,
|
||||||
|
Section,
|
||||||
|
Text,
|
||||||
|
Img,
|
||||||
|
} from '@react-email/components';
|
||||||
|
import { EmailLayout } from './components/EmailLayout';
|
||||||
|
import { Card } from './components/Card';
|
||||||
|
import { Button } from './components/Button';
|
||||||
|
import { theme } from './theme';
|
||||||
|
|
||||||
|
interface EventTicketEmailProps {
|
||||||
|
userName?: string;
|
||||||
|
eventName?: string;
|
||||||
|
eventDate?: string;
|
||||||
|
eventTime?: string;
|
||||||
|
venue?: string;
|
||||||
|
ticketType?: string;
|
||||||
|
amount?: string;
|
||||||
|
qrCodeUrl?: string;
|
||||||
|
ticketNumber?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EventTicketEmail = ({
|
||||||
|
userName = 'Kirubel',
|
||||||
|
eventName = 'Summer Music Festival 2024',
|
||||||
|
eventDate = 'July 15, 2024',
|
||||||
|
eventTime = '7:00 PM',
|
||||||
|
venue = 'City Park Amphitheater',
|
||||||
|
ticketType = 'General Admission',
|
||||||
|
amount = '$75.00',
|
||||||
|
qrCodeUrl = 'https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=EVENT-TICKET-12345',
|
||||||
|
ticketNumber = 'EVT-12345',
|
||||||
|
}: EventTicketEmailProps) => {
|
||||||
|
return (
|
||||||
|
<EmailLayout
|
||||||
|
preview={`Your ${eventName} ticket is ready!`}
|
||||||
|
>
|
||||||
|
<Section>
|
||||||
|
<Heading style={heading}>
|
||||||
|
Your Event Ticket is Ready! 🎫
|
||||||
|
</Heading>
|
||||||
|
<Text style={text}>
|
||||||
|
Hi {userName},
|
||||||
|
</Text>
|
||||||
|
<Text style={text}>
|
||||||
|
Thank you for your purchase! Your ticket for <strong>{eventName}</strong> has been confirmed. Please find your ticket details below.
|
||||||
|
</Text>
|
||||||
|
<Card>
|
||||||
|
<Section style={ticketInfo}>
|
||||||
|
<Text style={eventTitle}>{eventName}</Text>
|
||||||
|
<Section style={detailRow}>
|
||||||
|
<Text style={detailLabel}>Date:</Text>
|
||||||
|
<Text style={detailValue}>{eventDate}</Text>
|
||||||
|
</Section>
|
||||||
|
<Section style={detailRow}>
|
||||||
|
<Text style={detailLabel}>Time:</Text>
|
||||||
|
<Text style={detailValue}>{eventTime}</Text>
|
||||||
|
</Section>
|
||||||
|
<Section style={detailRow}>
|
||||||
|
<Text style={detailLabel}>Venue:</Text>
|
||||||
|
<Text style={detailValue}>{venue}</Text>
|
||||||
|
</Section>
|
||||||
|
<Section style={detailRow}>
|
||||||
|
<Text style={detailLabel}>Ticket Type:</Text>
|
||||||
|
<Text style={detailValue}>{ticketType}</Text>
|
||||||
|
</Section>
|
||||||
|
<Section style={detailRow}>
|
||||||
|
<Text style={detailLabel}>Ticket Number:</Text>
|
||||||
|
<Text style={detailValue}>{ticketNumber}</Text>
|
||||||
|
</Section>
|
||||||
|
<Section style={detailRow}>
|
||||||
|
<Text style={detailLabel}>Amount Paid:</Text>
|
||||||
|
<Text style={detailValue}>{amount}</Text>
|
||||||
|
</Section>
|
||||||
|
</Section>
|
||||||
|
</Card>
|
||||||
|
<Section style={qrSection}>
|
||||||
|
<Text style={qrTitle}>Your QR Code Ticket</Text>
|
||||||
|
<Text style={qrInstructions}>
|
||||||
|
Present this QR code at the venue entrance for quick access:
|
||||||
|
</Text>
|
||||||
|
<Section style={qrCodeContainer}>
|
||||||
|
<Img
|
||||||
|
src={qrCodeUrl}
|
||||||
|
width="200"
|
||||||
|
height="200"
|
||||||
|
alt="QR Code"
|
||||||
|
style={qrCode}
|
||||||
|
/>
|
||||||
|
</Section>
|
||||||
|
<Text style={qrNote}>
|
||||||
|
Save this email or take a screenshot for easy access at the event.
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
<Section style={buttonSection}>
|
||||||
|
<Button href="https://amba.app/tickets">
|
||||||
|
View in App
|
||||||
|
</Button>
|
||||||
|
</Section>
|
||||||
|
<Text style={text}>
|
||||||
|
We're excited to see you at the event!
|
||||||
|
</Text>
|
||||||
|
<Text style={text}>
|
||||||
|
Best regards,<br />
|
||||||
|
The Amba Team
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
</EmailLayout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const heading = {
|
||||||
|
color: theme.colors.primary,
|
||||||
|
fontSize: '28px',
|
||||||
|
fontWeight: '700',
|
||||||
|
margin: '0 0 24px',
|
||||||
|
};
|
||||||
|
|
||||||
|
const text = {
|
||||||
|
color: theme.colors.textDark,
|
||||||
|
fontSize: '16px',
|
||||||
|
lineHeight: '24px',
|
||||||
|
margin: '0 0 16px',
|
||||||
|
};
|
||||||
|
|
||||||
|
const ticketInfo = {
|
||||||
|
padding: '0',
|
||||||
|
};
|
||||||
|
|
||||||
|
const eventTitle = {
|
||||||
|
color: theme.colors.primary,
|
||||||
|
fontSize: '20px',
|
||||||
|
fontWeight: '700',
|
||||||
|
margin: '0 0 20px',
|
||||||
|
textAlign: 'center' as const,
|
||||||
|
};
|
||||||
|
|
||||||
|
const detailRow = {
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
marginBottom: '12px',
|
||||||
|
paddingBottom: '12px',
|
||||||
|
borderBottom: `1px solid ${theme.colors.border}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const detailLabel = {
|
||||||
|
color: theme.colors.text,
|
||||||
|
fontSize: '14px',
|
||||||
|
fontWeight: '600',
|
||||||
|
margin: '0',
|
||||||
|
flex: '1',
|
||||||
|
};
|
||||||
|
|
||||||
|
const detailValue = {
|
||||||
|
color: theme.colors.textDark,
|
||||||
|
fontSize: '14px',
|
||||||
|
margin: '0',
|
||||||
|
textAlign: 'right' as const,
|
||||||
|
flex: '1',
|
||||||
|
};
|
||||||
|
|
||||||
|
const qrSection = {
|
||||||
|
textAlign: 'center' as const,
|
||||||
|
margin: '32px 0',
|
||||||
|
padding: '24px',
|
||||||
|
backgroundColor: theme.colors.background,
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: `2px solid ${theme.colors.accent}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const qrTitle = {
|
||||||
|
color: theme.colors.primary,
|
||||||
|
fontSize: '18px',
|
||||||
|
fontWeight: '600',
|
||||||
|
margin: '0 0 8px',
|
||||||
|
};
|
||||||
|
|
||||||
|
const qrInstructions = {
|
||||||
|
color: theme.colors.textDark,
|
||||||
|
fontSize: '14px',
|
||||||
|
margin: '0 0 16px',
|
||||||
|
};
|
||||||
|
|
||||||
|
const qrCodeContainer = {
|
||||||
|
textAlign: 'center' as const,
|
||||||
|
margin: '16px 0',
|
||||||
|
};
|
||||||
|
|
||||||
|
const qrCode = {
|
||||||
|
margin: '0 auto',
|
||||||
|
border: `2px solid ${theme.colors.border}`,
|
||||||
|
borderRadius: '8px',
|
||||||
|
padding: '8px',
|
||||||
|
backgroundColor: theme.colors.background,
|
||||||
|
};
|
||||||
|
|
||||||
|
const qrNote = {
|
||||||
|
color: theme.colors.text,
|
||||||
|
fontSize: '12px',
|
||||||
|
margin: '16px 0 0',
|
||||||
|
fontStyle: 'italic',
|
||||||
|
};
|
||||||
|
|
||||||
|
const buttonSection = {
|
||||||
|
textAlign: 'center' as const,
|
||||||
|
margin: '32px 0',
|
||||||
|
};
|
||||||
|
|
||||||
|
EventTicketEmail.PreviewProps = {
|
||||||
|
userName: 'Kirubel',
|
||||||
|
eventName: 'Summer Music Festival 2024',
|
||||||
|
eventDate: 'July 15, 2024',
|
||||||
|
eventTime: '7:00 PM',
|
||||||
|
venue: 'City Park Amphitheater',
|
||||||
|
ticketType: 'General Admission',
|
||||||
|
amount: '$75.00',
|
||||||
|
qrCodeUrl: 'https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=EVENT-TICKET-12345',
|
||||||
|
ticketNumber: 'EVT-12345',
|
||||||
|
} as EventTicketEmailProps;
|
||||||
|
|
||||||
|
export default EventTicketEmail;
|
||||||
159
emails/payment-request.tsx
Normal file
159
emails/payment-request.tsx
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
import {
|
||||||
|
Heading,
|
||||||
|
Section,
|
||||||
|
Text,
|
||||||
|
} from '@react-email/components';
|
||||||
|
import { EmailLayout } from './components/EmailLayout';
|
||||||
|
import { Card } from './components/Card';
|
||||||
|
import { Button } from './components/Button';
|
||||||
|
import { theme } from './theme';
|
||||||
|
|
||||||
|
interface PaymentRequestEmailProps {
|
||||||
|
userName?: string;
|
||||||
|
requesterName?: string;
|
||||||
|
amount?: string;
|
||||||
|
description?: string;
|
||||||
|
requestId?: string;
|
||||||
|
dueDate?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PaymentRequestEmail = ({
|
||||||
|
userName = 'Kirubel',
|
||||||
|
requesterName = 'Sarah Johnson',
|
||||||
|
amount = '$250.00',
|
||||||
|
description = 'Dinner bill split - Italian Restaurant',
|
||||||
|
requestId = 'REQ-987654321',
|
||||||
|
dueDate = 'July 20, 2024',
|
||||||
|
}: PaymentRequestEmailProps) => {
|
||||||
|
return (
|
||||||
|
<EmailLayout
|
||||||
|
preview={`${requesterName} has sent you a payment request for ${amount}.`}
|
||||||
|
>
|
||||||
|
<Section>
|
||||||
|
<Heading style={heading}>
|
||||||
|
New Payment Request 💰
|
||||||
|
</Heading>
|
||||||
|
<Text style={text}>
|
||||||
|
Hi {userName},
|
||||||
|
</Text>
|
||||||
|
<Text style={text}>
|
||||||
|
<strong>{requesterName}</strong> has sent you a payment request. Here are the details:
|
||||||
|
</Text>
|
||||||
|
<Card>
|
||||||
|
<Section style={detailRow}>
|
||||||
|
<Text style={detailLabel}>Requested By:</Text>
|
||||||
|
<Text style={detailValue}>{requesterName}</Text>
|
||||||
|
</Section>
|
||||||
|
<Section style={detailRow}>
|
||||||
|
<Text style={detailLabel}>Amount:</Text>
|
||||||
|
<Text style={detailAmount}>{amount}</Text>
|
||||||
|
</Section>
|
||||||
|
<Section style={detailRow}>
|
||||||
|
<Text style={detailLabel}>Description:</Text>
|
||||||
|
<Text style={detailValue}>{description}</Text>
|
||||||
|
</Section>
|
||||||
|
<Section style={detailRow}>
|
||||||
|
<Text style={detailLabel}>Request ID:</Text>
|
||||||
|
<Text style={detailValue}>{requestId}</Text>
|
||||||
|
</Section>
|
||||||
|
{dueDate && (
|
||||||
|
<Section style={detailRow}>
|
||||||
|
<Text style={detailLabel}>Due Date:</Text>
|
||||||
|
<Text style={detailValue}>{dueDate}</Text>
|
||||||
|
</Section>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
<Text style={text}>
|
||||||
|
You can review and pay this request directly from the app. Payment is secure and will be processed immediately.
|
||||||
|
</Text>
|
||||||
|
<Section style={buttonSection}>
|
||||||
|
<Button href="https://amba.app/payment-requests">
|
||||||
|
View & Pay Request
|
||||||
|
</Button>
|
||||||
|
</Section>
|
||||||
|
<Text style={noteText}>
|
||||||
|
<strong>Note:</strong> This payment request will remain pending until you complete the payment. You'll receive a confirmation email once the payment is processed.
|
||||||
|
</Text>
|
||||||
|
<Text style={text}>
|
||||||
|
Best regards,<br />
|
||||||
|
The Amba Team
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
</EmailLayout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const heading = {
|
||||||
|
color: theme.colors.primary,
|
||||||
|
fontSize: '28px',
|
||||||
|
fontWeight: '700',
|
||||||
|
margin: '0 0 24px',
|
||||||
|
};
|
||||||
|
|
||||||
|
const text = {
|
||||||
|
color: theme.colors.textDark,
|
||||||
|
fontSize: '16px',
|
||||||
|
lineHeight: '24px',
|
||||||
|
margin: '0 0 16px',
|
||||||
|
};
|
||||||
|
|
||||||
|
const detailRow = {
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
marginBottom: '12px',
|
||||||
|
paddingBottom: '12px',
|
||||||
|
borderBottom: `1px solid ${theme.colors.border}`,
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
};
|
||||||
|
|
||||||
|
const detailLabel = {
|
||||||
|
color: theme.colors.text,
|
||||||
|
fontSize: '14px',
|
||||||
|
fontWeight: '600',
|
||||||
|
margin: '0',
|
||||||
|
flex: '1',
|
||||||
|
};
|
||||||
|
|
||||||
|
const detailValue = {
|
||||||
|
color: theme.colors.textDark,
|
||||||
|
fontSize: '14px',
|
||||||
|
margin: '0',
|
||||||
|
textAlign: 'right' as const,
|
||||||
|
flex: '1',
|
||||||
|
};
|
||||||
|
|
||||||
|
const detailAmount = {
|
||||||
|
color: theme.colors.primary,
|
||||||
|
fontSize: '18px',
|
||||||
|
fontWeight: '700',
|
||||||
|
margin: '0',
|
||||||
|
textAlign: 'right' as const,
|
||||||
|
flex: '1',
|
||||||
|
};
|
||||||
|
|
||||||
|
const buttonSection = {
|
||||||
|
textAlign: 'center' as const,
|
||||||
|
margin: '32px 0',
|
||||||
|
};
|
||||||
|
|
||||||
|
const noteText = {
|
||||||
|
color: theme.colors.text,
|
||||||
|
fontSize: '14px',
|
||||||
|
lineHeight: '20px',
|
||||||
|
margin: '24px 0 16px',
|
||||||
|
padding: '12px',
|
||||||
|
backgroundColor: '#fff9e6',
|
||||||
|
borderRadius: '4px',
|
||||||
|
borderLeft: `3px solid ${theme.colors.accent}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
PaymentRequestEmail.PreviewProps = {
|
||||||
|
userName: 'Kirubel',
|
||||||
|
requesterName: 'Sarah Johnson',
|
||||||
|
amount: '$250.00',
|
||||||
|
description: 'Dinner bill split - Italian Restaurant',
|
||||||
|
requestId: 'REQ-987654321',
|
||||||
|
dueDate: 'July 20, 2024',
|
||||||
|
} as PaymentRequestEmailProps;
|
||||||
|
|
||||||
|
export default PaymentRequestEmail;
|
||||||
3
emails/react-email.d.ts
vendored
Normal file
3
emails/react-email.d.ts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
declare module '@react-email/components' {
|
||||||
|
export * from '@react-email/components';
|
||||||
|
}
|
||||||
166
emails/referral-success.tsx
Normal file
166
emails/referral-success.tsx
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
import {
|
||||||
|
Heading,
|
||||||
|
Section,
|
||||||
|
Text,
|
||||||
|
} from '@react-email/components';
|
||||||
|
import { EmailLayout } from './components/EmailLayout';
|
||||||
|
import { Card } from './components/Card';
|
||||||
|
import { Button } from './components/Button';
|
||||||
|
import { theme } from './theme';
|
||||||
|
|
||||||
|
interface ReferralSuccessEmailProps {
|
||||||
|
userName?: string;
|
||||||
|
referredUserName?: string;
|
||||||
|
referralCode?: string;
|
||||||
|
rewardAmount?: string;
|
||||||
|
transactionAmount?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ReferralSuccessEmail = ({
|
||||||
|
userName = 'Kirubel',
|
||||||
|
referredUserName = 'Alex Martinez',
|
||||||
|
referralCode = 'AMB-KIRU-2024',
|
||||||
|
rewardAmount = '$10.00',
|
||||||
|
transactionAmount = '$100.00',
|
||||||
|
}: ReferralSuccessEmailProps) => {
|
||||||
|
return (
|
||||||
|
<EmailLayout
|
||||||
|
preview={`Great news! Someone used your referral code and you earned ${rewardAmount}!`}
|
||||||
|
>
|
||||||
|
<Section>
|
||||||
|
<Heading style={heading}>
|
||||||
|
Referral Reward Earned! 🎉
|
||||||
|
</Heading>
|
||||||
|
<Text style={text}>
|
||||||
|
Hi {userName},
|
||||||
|
</Text>
|
||||||
|
<Text style={text}>
|
||||||
|
Excellent news! <strong>{referredUserName}</strong> has used your referral code <strong>{referralCode}</strong> to make a payment, and you've earned a reward!
|
||||||
|
</Text>
|
||||||
|
<Card>
|
||||||
|
<Section style={rewardSection}>
|
||||||
|
<Text style={rewardLabel}>Your Reward</Text>
|
||||||
|
<Text style={rewardAmount}>{rewardAmount}</Text>
|
||||||
|
<Text style={rewardNote}>
|
||||||
|
This reward has been automatically added to your account.
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
<Section style={detailRow}>
|
||||||
|
<Text style={detailLabel}>Referred By:</Text>
|
||||||
|
<Text style={detailValue}>{userName}</Text>
|
||||||
|
</Section>
|
||||||
|
<Section style={detailRow}>
|
||||||
|
<Text style={detailLabel}>New User:</Text>
|
||||||
|
<Text style={detailValue}>{referredUserName}</Text>
|
||||||
|
</Section>
|
||||||
|
<Section style={detailRow}>
|
||||||
|
<Text style={detailLabel}>Referral Code Used:</Text>
|
||||||
|
<Text style={detailValue}>{referralCode}</Text>
|
||||||
|
</Section>
|
||||||
|
<Section style={detailRow}>
|
||||||
|
<Text style={detailLabel}>Transaction Amount:</Text>
|
||||||
|
<Text style={detailValue}>{transactionAmount}</Text>
|
||||||
|
</Section>
|
||||||
|
</Card>
|
||||||
|
<Text style={text}>
|
||||||
|
Keep sharing your referral code with friends and family to earn more rewards! Every successful referral earns you {rewardAmount}.
|
||||||
|
</Text>
|
||||||
|
<Section style={buttonSection}>
|
||||||
|
<Button href="https://amba.app/referrals">
|
||||||
|
View Referral Dashboard
|
||||||
|
</Button>
|
||||||
|
</Section>
|
||||||
|
<Text style={text}>
|
||||||
|
Thank you for helping grow the Amba community!
|
||||||
|
</Text>
|
||||||
|
<Text style={text}>
|
||||||
|
Best regards,<br />
|
||||||
|
The Amba Team
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
</EmailLayout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const heading = {
|
||||||
|
color: theme.colors.primary,
|
||||||
|
fontSize: '28px',
|
||||||
|
fontWeight: '700',
|
||||||
|
margin: '0 0 24px',
|
||||||
|
};
|
||||||
|
|
||||||
|
const text = {
|
||||||
|
color: theme.colors.textDark,
|
||||||
|
fontSize: '16px',
|
||||||
|
lineHeight: '24px',
|
||||||
|
margin: '0 0 16px',
|
||||||
|
};
|
||||||
|
|
||||||
|
const rewardSection = {
|
||||||
|
textAlign: 'center' as const,
|
||||||
|
padding: '20px 0',
|
||||||
|
marginBottom: '20px',
|
||||||
|
borderBottom: `2px solid ${theme.colors.accent}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const rewardLabel = {
|
||||||
|
color: theme.colors.text,
|
||||||
|
fontSize: '14px',
|
||||||
|
fontWeight: '600',
|
||||||
|
margin: '0 0 8px',
|
||||||
|
textTransform: 'uppercase' as const,
|
||||||
|
letterSpacing: '1px',
|
||||||
|
};
|
||||||
|
|
||||||
|
const rewardAmount = {
|
||||||
|
color: theme.colors.primary,
|
||||||
|
fontSize: '36px',
|
||||||
|
fontWeight: '700',
|
||||||
|
margin: '0 0 8px',
|
||||||
|
};
|
||||||
|
|
||||||
|
const rewardNote = {
|
||||||
|
color: theme.colors.text,
|
||||||
|
fontSize: '12px',
|
||||||
|
margin: '0',
|
||||||
|
fontStyle: 'italic',
|
||||||
|
};
|
||||||
|
|
||||||
|
const detailRow = {
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
marginBottom: '12px',
|
||||||
|
paddingBottom: '12px',
|
||||||
|
borderBottom: `1px solid ${theme.colors.border}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const detailLabel = {
|
||||||
|
color: theme.colors.text,
|
||||||
|
fontSize: '14px',
|
||||||
|
fontWeight: '600',
|
||||||
|
margin: '0',
|
||||||
|
flex: '1',
|
||||||
|
};
|
||||||
|
|
||||||
|
const detailValue = {
|
||||||
|
color: theme.colors.textDark,
|
||||||
|
fontSize: '14px',
|
||||||
|
margin: '0',
|
||||||
|
textAlign: 'right' as const,
|
||||||
|
flex: '1',
|
||||||
|
};
|
||||||
|
|
||||||
|
const buttonSection = {
|
||||||
|
textAlign: 'center' as const,
|
||||||
|
margin: '32px 0',
|
||||||
|
};
|
||||||
|
|
||||||
|
ReferralSuccessEmail.PreviewProps = {
|
||||||
|
userName: 'Kirubel',
|
||||||
|
referredUserName: 'Alex Martinez',
|
||||||
|
referralCode: 'AMB-KIRU-2024',
|
||||||
|
rewardAmount: '$10.00',
|
||||||
|
transactionAmount: '$100.00',
|
||||||
|
} as ReferralSuccessEmailProps;
|
||||||
|
|
||||||
|
export default ReferralSuccessEmail;
|
||||||
22
emails/theme.ts
Normal file
22
emails/theme.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
export const theme = {
|
||||||
|
colors: {
|
||||||
|
primary: '#2d5016', // Dark green
|
||||||
|
primaryLight: '#4a7c2a', // Medium green
|
||||||
|
accent: '#7cb342', // Light green
|
||||||
|
accentLight: '#aed581', // Lighter green
|
||||||
|
text: '#8d6e63', // Light brown/orange for text
|
||||||
|
textDark: '#5d4037', // Darker brown for headings
|
||||||
|
background: '#ffffff',
|
||||||
|
border: '#e0e0e0',
|
||||||
|
},
|
||||||
|
fonts: {
|
||||||
|
sans: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
||||||
|
},
|
||||||
|
spacing: {
|
||||||
|
xs: '8px',
|
||||||
|
sm: '16px',
|
||||||
|
md: '24px',
|
||||||
|
lg: '32px',
|
||||||
|
xl: '48px',
|
||||||
|
},
|
||||||
|
};
|
||||||
118
emails/transaction-complete.tsx
Normal file
118
emails/transaction-complete.tsx
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
import {
|
||||||
|
Heading,
|
||||||
|
Section,
|
||||||
|
Text,
|
||||||
|
} from '@react-email/components';
|
||||||
|
import { EmailLayout } from './components/EmailLayout';
|
||||||
|
import { Card } from './components/Card';
|
||||||
|
import { theme } from './theme';
|
||||||
|
|
||||||
|
interface TransactionCompleteEmailProps {
|
||||||
|
userName?: string;
|
||||||
|
amount?: string;
|
||||||
|
transactionId?: string;
|
||||||
|
recipient?: string;
|
||||||
|
date?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TransactionCompleteEmail = ({
|
||||||
|
userName = 'Kirubel',
|
||||||
|
amount = '$150.00',
|
||||||
|
transactionId = 'TXN-123456789',
|
||||||
|
recipient = 'John Doe',
|
||||||
|
date = new Date().toLocaleDateString(),
|
||||||
|
}: TransactionCompleteEmailProps) => {
|
||||||
|
return (
|
||||||
|
<EmailLayout
|
||||||
|
preview={`Your payment of ${amount} to ${recipient} was successful.`}
|
||||||
|
>
|
||||||
|
<Section>
|
||||||
|
<Heading style={heading}>
|
||||||
|
Transaction Complete! ✅
|
||||||
|
</Heading>
|
||||||
|
<Text style={text}>
|
||||||
|
Hi {userName},
|
||||||
|
</Text>
|
||||||
|
<Text style={text}>
|
||||||
|
Your payment has been successfully processed. Here are the details:
|
||||||
|
</Text>
|
||||||
|
<Card>
|
||||||
|
<Section style={detailRow}>
|
||||||
|
<Text style={detailLabel}>Amount:</Text>
|
||||||
|
<Text style={detailValue}>{amount}</Text>
|
||||||
|
</Section>
|
||||||
|
<Section style={detailRow}>
|
||||||
|
<Text style={detailLabel}>Recipient:</Text>
|
||||||
|
<Text style={detailValue}>{recipient}</Text>
|
||||||
|
</Section>
|
||||||
|
<Section style={detailRow}>
|
||||||
|
<Text style={detailLabel}>Transaction ID:</Text>
|
||||||
|
<Text style={detailValue}>{transactionId}</Text>
|
||||||
|
</Section>
|
||||||
|
<Section style={detailRow}>
|
||||||
|
<Text style={detailLabel}>Date:</Text>
|
||||||
|
<Text style={detailValue}>{date}</Text>
|
||||||
|
</Section>
|
||||||
|
</Card>
|
||||||
|
<Text style={text}>
|
||||||
|
The funds have been transferred and the recipient has been notified. You can view this transaction in your transaction history at any time.
|
||||||
|
</Text>
|
||||||
|
<Text style={text}>
|
||||||
|
If you have any questions or concerns about this transaction, please contact our support team.
|
||||||
|
</Text>
|
||||||
|
<Text style={text}>
|
||||||
|
Best regards,<br />
|
||||||
|
The Amba Team
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
</EmailLayout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const heading = {
|
||||||
|
color: theme.colors.primary,
|
||||||
|
fontSize: '28px',
|
||||||
|
fontWeight: '700',
|
||||||
|
margin: '0 0 24px',
|
||||||
|
};
|
||||||
|
|
||||||
|
const text = {
|
||||||
|
color: theme.colors.textDark,
|
||||||
|
fontSize: '16px',
|
||||||
|
lineHeight: '24px',
|
||||||
|
margin: '0 0 16px',
|
||||||
|
};
|
||||||
|
|
||||||
|
const detailRow = {
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
marginBottom: '12px',
|
||||||
|
paddingBottom: '12px',
|
||||||
|
borderBottom: `1px solid ${theme.colors.border}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const detailLabel = {
|
||||||
|
color: theme.colors.text,
|
||||||
|
fontSize: '14px',
|
||||||
|
fontWeight: '600',
|
||||||
|
margin: '0',
|
||||||
|
flex: '1',
|
||||||
|
};
|
||||||
|
|
||||||
|
const detailValue = {
|
||||||
|
color: theme.colors.textDark,
|
||||||
|
fontSize: '14px',
|
||||||
|
margin: '0',
|
||||||
|
textAlign: 'right' as const,
|
||||||
|
flex: '1',
|
||||||
|
};
|
||||||
|
|
||||||
|
TransactionCompleteEmail.PreviewProps = {
|
||||||
|
userName: 'Kirubel',
|
||||||
|
amount: '$150.00',
|
||||||
|
transactionId: 'TXN-123456789',
|
||||||
|
recipient: 'John Doe',
|
||||||
|
date: new Date().toLocaleDateString(),
|
||||||
|
} as TransactionCompleteEmailProps;
|
||||||
|
|
||||||
|
export default TransactionCompleteEmail;
|
||||||
12
emails/utils/logoDataUri.ts
Normal file
12
emails/utils/logoDataUri.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
// SVG logo as data URI for email compatibility
|
||||||
|
// In production, you should host this logo and use the hosted URL
|
||||||
|
|
||||||
|
// SVG with fill color set to match theme
|
||||||
|
const logoSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 86.42 80.35"><g><path fill="#2d5016" d="m86.42,70.54h-8.94l-4.29-7.02c2.34-1.06,4.47-2.49,6.34-4.25l6.9,11.27Zm-3.16-28.6c0-10.99-8.94-19.92-19.92-19.92h-18.76l-25.06,40.9h-5.91L43.21,14.59l2.02,3.29h8.94L43.22,0,0,70.54h20.49c2.05,0,3.96-1.07,5.02-2.81l20.9-34.12,2.43-3.97h14.49c6.78,0,12.3,5.52,12.3,12.3,0,2.68-.86,5.16-2.33,7.18-1.51,2.1-3.67,3.7-6.18,4.51-1.2.39-2.47.6-3.8.6h-16.88c-3.48,0-6.77,1.85-8.59,4.81l-13.05,21.3h5.64c2.04,0,3.95-1.07,5.02-2.81l8.89-14.51c.44-.72,1.24-1.17,2.09-1.17h16.88c2.78,0,5.44-.57,7.85-1.61,2.37-1.02,4.51-2.49,6.31-4.31,3.57-3.6,5.77-8.56,5.77-14.01Zm-17.3,8.07c1.69-.55,3.21-1.65,4.25-3.11.34-.46.62-.95.84-1.46l-7.25-11.83h-8.94l10.19,16.64c.31-.05.61-.14.91-.24Z"/></g></svg>`;
|
||||||
|
|
||||||
|
// Encode SVG for data URI
|
||||||
|
const encodedSvg = encodeURIComponent(logoSvg);
|
||||||
|
export const logoDataUri = `data:image/svg+xml;charset=utf-8,${encodedSvg}`;
|
||||||
|
|
||||||
|
// For production, replace with your hosted logo URL:
|
||||||
|
// export const logoUrl = 'https://yourdomain.com/logo.svg';
|
||||||
69
emails/welcome.tsx
Normal file
69
emails/welcome.tsx
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
import {
|
||||||
|
Heading,
|
||||||
|
Section,
|
||||||
|
Text,
|
||||||
|
} from '@react-email/components';
|
||||||
|
import { EmailLayout } from './components/EmailLayout';
|
||||||
|
import { Button } from './components/Button';
|
||||||
|
import { theme } from './theme';
|
||||||
|
|
||||||
|
interface WelcomeEmailProps {
|
||||||
|
userName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WelcomeEmail = ({ userName = 'Kirubel' }: WelcomeEmailProps) => {
|
||||||
|
return (
|
||||||
|
<EmailLayout
|
||||||
|
preview="Welcome to Amba! We're excited to have you join us."
|
||||||
|
>
|
||||||
|
<Section>
|
||||||
|
<Heading style={heading}>
|
||||||
|
Hi {userName}! 👋
|
||||||
|
</Heading>
|
||||||
|
<Text style={text}>
|
||||||
|
Welcome to Amba! We're thrilled to have you join our community. Amba makes it easy to manage your payments, send money to friends, and handle all your financial transactions seamlessly.
|
||||||
|
</Text>
|
||||||
|
<Text style={text}>
|
||||||
|
Get started by adding your first payment card or exploring the app to see all the amazing features we have to offer.
|
||||||
|
</Text>
|
||||||
|
<Section style={buttonSection}>
|
||||||
|
<Button href="https://amba.app/get-started">
|
||||||
|
Get Started
|
||||||
|
</Button>
|
||||||
|
</Section>
|
||||||
|
<Text style={text}>
|
||||||
|
If you have any questions, feel free to reach out to our support team. We're here to help!
|
||||||
|
</Text>
|
||||||
|
<Text style={text}>
|
||||||
|
Best regards,<br />
|
||||||
|
The Amba Team
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
</EmailLayout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const heading = {
|
||||||
|
color: theme.colors.primary,
|
||||||
|
fontSize: '28px',
|
||||||
|
fontWeight: '700',
|
||||||
|
margin: '0 0 24px',
|
||||||
|
};
|
||||||
|
|
||||||
|
const text = {
|
||||||
|
color: theme.colors.textDark,
|
||||||
|
fontSize: '16px',
|
||||||
|
lineHeight: '24px',
|
||||||
|
margin: '0 0 16px',
|
||||||
|
};
|
||||||
|
|
||||||
|
const buttonSection = {
|
||||||
|
textAlign: 'center' as const,
|
||||||
|
margin: '32px 0',
|
||||||
|
};
|
||||||
|
|
||||||
|
WelcomeEmail.PreviewProps = {
|
||||||
|
userName: 'Kirubel',
|
||||||
|
} as WelcomeEmailProps;
|
||||||
|
|
||||||
|
export default WelcomeEmail;
|
||||||
6
next.config.js
Normal file
6
next.config.js
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {
|
||||||
|
reactStrictMode: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = nextConfig;
|
||||||
26
package.json
Normal file
26
package.json
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"name": "amba-emails",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Email templates for Amba app",
|
||||||
|
"scripts": {
|
||||||
|
"email": "email dev",
|
||||||
|
"build": "email export",
|
||||||
|
"preview": "next dev"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@react-email/components": "^0.0.25",
|
||||||
|
"@react-email/render": "^1.0.4",
|
||||||
|
"next": "^14.2.0",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"qrcode": "^1.5.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.14.0",
|
||||||
|
"@types/qrcode": "^1.5.5",
|
||||||
|
"@types/react": "^18.3.0",
|
||||||
|
"@types/react-dom": "^18.3.0",
|
||||||
|
"react-email": "^2.0.0",
|
||||||
|
"typescript": "^5.4.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
6
public/logo.svg
Normal file
6
public/logo.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 86.42 80.35">
|
||||||
|
<g id="Layer_1-2" data-name="Layer 1">
|
||||||
|
<path d="m86.42,70.54h-8.94l-4.29-7.02c2.34-1.06,4.47-2.49,6.34-4.25l6.9,11.27Zm-3.16-28.6c0-10.99-8.94-19.92-19.92-19.92h-18.76l-25.06,40.9h-5.91L43.21,14.59l2.02,3.29h8.94L43.22,0,0,70.54h20.49c2.05,0,3.96-1.07,5.02-2.81l20.9-34.12,2.43-3.97h14.49c6.78,0,12.3,5.52,12.3,12.3,0,2.68-.86,5.16-2.33,7.18-1.51,2.1-3.67,3.7-6.18,4.51-1.2.39-2.47.6-3.8.6h-16.88c-3.48,0-6.77,1.85-8.59,4.81l-13.05,21.3h5.64c2.04,0,3.95-1.07,5.02-2.81l8.89-14.51c.44-.72,1.24-1.17,2.09-1.17h16.88c2.78,0,5.44-.57,7.85-1.61,2.37-1.02,4.51-2.49,6.31-4.31,3.57-3.6,5.77-8.56,5.77-14.01Zm-17.3,8.07c1.69-.55,3.21-1.65,4.25-3.11.34-.46.62-.95.84-1.46l-7.25-11.83h-8.94l10.19,16.64c.31-.05.61-.14.91-.24Z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 877 B |
28
tsconfig.json
Normal file
28
tsconfig.json
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"incremental": true,
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user