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