style: tweaked email templates
This commit is contained in:
14
components/emails/components/footer.tsx
Normal file
14
components/emails/components/footer.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export function Footer() {
|
||||||
|
return (
|
||||||
|
<footer className='mt-8 border-t border-gray-200 pt-6'>
|
||||||
|
<div className='mb-4 ml-8 flex items-center justify-between'>
|
||||||
|
<div>
|
||||||
|
<h4 className='text-lg font-semibold text-gray-700'>Contact Us</h4>
|
||||||
|
<p className='text-gray-600'>FromPixels</p>
|
||||||
|
<p className='text-gray-600'>Email: info@frompixels.com</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className='text-sm text-gray-500'></p>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
||||||
11
components/emails/components/note.tsx
Normal file
11
components/emails/components/note.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
type NoteProps = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Note({ children }: NoteProps) {
|
||||||
|
return (
|
||||||
|
<div className='mt-8 bg-gray-100 px-6 py-4 dark:bg-gray-800'>
|
||||||
|
<div className='text-sm text-gray-600 dark:text-gray-400'>{children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Link } from '../custom/link';
|
||||||
|
import { Note } from './components/note';
|
||||||
import Email from './template';
|
import Email from './template';
|
||||||
|
|
||||||
export default function ConfirmationTemplate(code: string) {
|
export default function ConfirmationTemplate(code: string) {
|
||||||
@@ -7,14 +9,25 @@ export default function ConfirmationTemplate(code: string) {
|
|||||||
<Email
|
<Email
|
||||||
title={'Welcome!'}
|
title={'Welcome!'}
|
||||||
body={
|
body={
|
||||||
<>
|
<div className='mt-8'>
|
||||||
Thank you for subscribing. Please confirm your email address by
|
<p className='text-base text-gray-700 dark:text-gray-400'>
|
||||||
clicking{' '}
|
Dear subscriber,
|
||||||
<a href={`${process.env.HOME_URL}/confirmation?code=${code}`}>
|
</p>
|
||||||
here
|
<p className='mt-2 text-base text-gray-700 dark:text-gray-400'>
|
||||||
</a>
|
thank you for subscribing to our newsletter! Please click the
|
||||||
.
|
button below to confirm your subscription.
|
||||||
</>
|
</p>
|
||||||
|
<div className='mt-8 flex justify-center'>
|
||||||
|
<Link
|
||||||
|
path={`${process.env.HOME_URL}/confirmation?code=${code}`}
|
||||||
|
text='Confirm Subscription'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Note>
|
||||||
|
If you didn't subscribe to our newsletter, please ignore this
|
||||||
|
email.
|
||||||
|
</Note>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,51 +4,85 @@ import { Section } from '@react-email/section';
|
|||||||
import { Text } from '@react-email/text';
|
import { Text } from '@react-email/text';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { NewsSchema } from '../../utils/schemas';
|
import { NewsSchema } from '../../utils/schemas';
|
||||||
|
import { Footer } from './components/footer';
|
||||||
|
|
||||||
export default function NewsletterTemplate(
|
export default function NewsletterTemplate(
|
||||||
stories: z.infer<typeof NewsSchema>[]
|
stories: z.infer<typeof NewsSchema>[]
|
||||||
) {
|
) {
|
||||||
|
const sayings = [
|
||||||
|
'hot off the press',
|
||||||
|
'straight from the oven',
|
||||||
|
"straight from the horse's mouth",
|
||||||
|
'brand spanking new',
|
||||||
|
'fresh as a daisy',
|
||||||
|
'straight out of the box',
|
||||||
|
'straight off the assembly line',
|
||||||
|
'hot out of the kitchen',
|
||||||
|
'just minted',
|
||||||
|
'freshly brewed',
|
||||||
|
'just off the production line'
|
||||||
|
];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subject: `What's new from Hackernews?`,
|
subject: `What's new from Hackernews?`,
|
||||||
template: (
|
template: (
|
||||||
<Html>
|
<Html>
|
||||||
<Section style={main}>
|
<Section style={main}>
|
||||||
<Container style={container}>
|
<div className='mx-auto w-full max-w-2xl overflow-hidden rounded-lg bg-white shadow-lg'>
|
||||||
<Text style={paragraph}>
|
<div className='text-center '>
|
||||||
{stories.map(story => {
|
<h1 className='my-4 text-3xl font-bold'>Good day!</h1>
|
||||||
return (
|
<p>
|
||||||
<div
|
Here is something{' '}
|
||||||
key={story.id}
|
{sayings[Math.floor(Math.random() * sayings.length)]}:
|
||||||
style={{
|
</p>
|
||||||
padding: '10px',
|
</div>
|
||||||
border: '1px solid #ccc',
|
<Container style={container}>
|
||||||
boxShadow: '2px 2px 5px rgba(0, 0, 0, 0.1)'
|
<Text>
|
||||||
}}
|
{stories.map(story => {
|
||||||
>
|
return (
|
||||||
<h1>{story.title}</h1>
|
<div
|
||||||
<p>{story.by}</p>
|
key={story.id}
|
||||||
{story.text && (
|
className='mt-8 rounded-lg border bg-card text-card-foreground shadow-sm'
|
||||||
<p
|
data-v0-t='card'
|
||||||
dangerouslySetInnerHTML={{
|
>
|
||||||
__html:
|
<div className='flex flex-col space-y-1.5 p-6'>
|
||||||
story.text.length > 500
|
<h2 className='text-2xl font-semibold'>
|
||||||
? story.text.substring(0, 500) + '...'
|
{story.title}
|
||||||
: story.text
|
</h2>
|
||||||
}}
|
<p>{story.by}</p>
|
||||||
/>
|
</div>
|
||||||
)}
|
{story.text && (
|
||||||
{story.url && (
|
<div className='px-6'>
|
||||||
<p>
|
<p
|
||||||
<a href={story.url} style={{ textAlign: 'right' }}>
|
className='mb-4'
|
||||||
Read more
|
dangerouslySetInnerHTML={{
|
||||||
</a>
|
__html:
|
||||||
</p>
|
story.text.length > 500
|
||||||
)}
|
? story.text.substring(0, 500) + '...'
|
||||||
</div>
|
: story.text
|
||||||
);
|
}}
|
||||||
})}
|
/>
|
||||||
</Text>
|
</div>
|
||||||
</Container>
|
)}
|
||||||
|
{story.url && (
|
||||||
|
<div className='p-4 text-right'>
|
||||||
|
<p>
|
||||||
|
<a
|
||||||
|
href={story.url}
|
||||||
|
className='inline-flex h-10 items-center justify-center rounded-md bg-blue-500 px-4 py-2 text-sm font-medium text-primary-foreground ring-offset-background transition-colors hover:bg-blue-700/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50'
|
||||||
|
>
|
||||||
|
Read more
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
</Container>
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
</Html>
|
</Html>
|
||||||
)
|
)
|
||||||
@@ -64,9 +98,3 @@ const container = {
|
|||||||
padding: '20px 0 48px',
|
padding: '20px 0 48px',
|
||||||
width: '580px'
|
width: '580px'
|
||||||
};
|
};
|
||||||
|
|
||||||
const paragraph = {
|
|
||||||
fontSize: '18px',
|
|
||||||
lineHeight: '1.4',
|
|
||||||
color: '#484848'
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Container } from '@react-email/container';
|
|||||||
import { Html } from '@react-email/html';
|
import { Html } from '@react-email/html';
|
||||||
import { Section } from '@react-email/section';
|
import { Section } from '@react-email/section';
|
||||||
import { Text } from '@react-email/text';
|
import { Text } from '@react-email/text';
|
||||||
|
import { Footer } from './components/footer';
|
||||||
|
|
||||||
type EmailProps = {
|
type EmailProps = {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -11,33 +12,31 @@ type EmailProps = {
|
|||||||
export default function Email({ title, body }: EmailProps) {
|
export default function Email({ title, body }: EmailProps) {
|
||||||
return (
|
return (
|
||||||
<Html>
|
<Html>
|
||||||
<Section style={main}>
|
<Section
|
||||||
|
className='mx-auto w-full max-w-2xl overflow-hidden rounded-lg bg-white shadow-lg'
|
||||||
|
style={main}
|
||||||
|
>
|
||||||
<Container style={container}>
|
<Container style={container}>
|
||||||
<Text style={heading}>{title}</Text>
|
<h1 className='mt-4 text-center text-3xl font-bold'>{title}</h1>
|
||||||
<Text style={paragraph}>{body}</Text>
|
<Text style={paragraph}>{body}</Text>
|
||||||
</Container>
|
</Container>
|
||||||
|
<Footer />
|
||||||
</Section>
|
</Section>
|
||||||
</Html>
|
</Html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const main = {
|
const main = {
|
||||||
backgroundColor: '#ffffff',
|
backgroundColor: '#ffffff'
|
||||||
};
|
};
|
||||||
|
|
||||||
const container = {
|
const container = {
|
||||||
margin: '0 auto',
|
margin: '0 auto',
|
||||||
padding: '20px 0 48px',
|
padding: '20px 0 48px',
|
||||||
width: '580px',
|
width: '580px'
|
||||||
};
|
|
||||||
|
|
||||||
const heading = {
|
|
||||||
fontSize: '24px',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
marginBottom: '16px',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const paragraph = {
|
const paragraph = {
|
||||||
fontSize: '16px',
|
fontSize: '16px',
|
||||||
marginBottom: '16px',
|
marginBottom: '16px'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Link } from '../custom/link';
|
||||||
|
import { Note } from './components/note';
|
||||||
import Email from './template';
|
import Email from './template';
|
||||||
|
|
||||||
export default function UnsubscribeTemplate() {
|
export default function UnsubscribeTemplate() {
|
||||||
@@ -6,8 +8,26 @@ export default function UnsubscribeTemplate() {
|
|||||||
template: (
|
template: (
|
||||||
<Email
|
<Email
|
||||||
title="We're sad you're leaving :("
|
title="We're sad you're leaving :("
|
||||||
body={<>You have unsubscribed from the newsletter.</>}
|
body={
|
||||||
|
<div className='mt-8'>
|
||||||
|
<p className='mt-2 text-base text-gray-700 dark:text-gray-400'>
|
||||||
|
You have been successfully unsubscribed from our newsletter. You
|
||||||
|
won't receive any further communications from us unless you
|
||||||
|
explicitly opt-in again.
|
||||||
|
</p>
|
||||||
|
<div className='my-8 flex justify-center'>
|
||||||
|
<Link path='/' text='Re-subscribe' />
|
||||||
|
</div>
|
||||||
|
<Note>
|
||||||
|
If you have any questions or concerns, please feel free to{' '}
|
||||||
|
<a className='text-blue-500 underline' href='#'>
|
||||||
|
contact us
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</Note>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
),
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user