style: general tweaking
This commit is contained in:
@@ -29,3 +29,5 @@ RESEND_FROM=""
|
||||
SECRET_HASH=""
|
||||
HOME_URL=""
|
||||
MAINTENANCE_MODE=0
|
||||
BRAND_NAME=""
|
||||
BRAND_EMAIL=""
|
||||
@@ -1,5 +1,11 @@
|
||||
# Hackernews newsletter
|
||||
|
||||
## Next up
|
||||
|
||||
- Batch email (Resend: ETA early 2024)
|
||||
- Custom url shortener for links in the newsletter
|
||||
- Cron every 10 minutes: people are more likely to open the newsletter if delivered around the time when they subscribed (if cron becomes not enough, then the cost of sending all the emails might be a bigger issue)
|
||||
|
||||
## Vercel basics
|
||||
|
||||
Install vercel cli
|
||||
|
||||
@@ -6,10 +6,10 @@ export async function GET() {
|
||||
orderBy: {
|
||||
createdAt: 'desc'
|
||||
},
|
||||
take: 50
|
||||
take: 100
|
||||
});
|
||||
|
||||
if (news && news.length === 50) {
|
||||
if (news && news.length === 100) {
|
||||
return ApiResponse(200, JSON.stringify(news));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { z } from 'zod';
|
||||
import { getRandomColor } from '../../../../utils/getRandomColor';
|
||||
import { NewsSchema } from '../../../../utils/schemas';
|
||||
|
||||
type CardContentProps = {
|
||||
@@ -7,15 +8,6 @@ type CardContentProps = {
|
||||
side: boolean;
|
||||
};
|
||||
|
||||
function getRandomColor() {
|
||||
const letters = '456789ABCDEF';
|
||||
let color = '#';
|
||||
for (let i = 0; i < 6; i++) {
|
||||
color += letters[Math.floor(Math.random() * 12)];
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
export function TileContent({ story, side }: CardContentProps) {
|
||||
const [firstColor, setFirstColor] = useState(getRandomColor());
|
||||
const [secondColor, setSecondColor] = useState(getRandomColor());
|
||||
@@ -32,14 +24,15 @@ export function TileContent({ story, side }: CardContentProps) {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`h-40 w-40 overflow-hidden p-6 shadow-sm`}
|
||||
className={`h-40 w-40 overflow-hidden rounded-lg p-6 shadow-sm`}
|
||||
style={{
|
||||
backgroundColor: `${color}`
|
||||
}}
|
||||
>
|
||||
<h1 className='overflow-auto font-semibold'>{story.title}</h1>
|
||||
<p className='overflow-auto italic'>{story.by}</p>
|
||||
<p className='overflow-auto italic'>by {story.by}</p>
|
||||
<div
|
||||
className='rounded-lg'
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
import { getRandomColor } from '../../../utils/getRandomColor';
|
||||
|
||||
export function Footer() {
|
||||
const background = getRandomColor();
|
||||
|
||||
return (
|
||||
<footer className='mt-8 border-t border-gray-200 pt-6'>
|
||||
<div className='mb-4 ml-8 flex items-center justify-between'>
|
||||
<footer
|
||||
className='mt-8 bg-blue-200 pt-6 text-black'
|
||||
style={{ backgroundColor: `${background}` }}
|
||||
>
|
||||
<div className='ml-8 flex items-center justify-between pb-4'>
|
||||
<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>
|
||||
<h4 className='text-lg font-semibold'>Contact Us</h4>
|
||||
<p>{process.env.BRAND_NAME}</p>
|
||||
<a href={`mailto:${process.env.BRAND_EMAIL}`}>
|
||||
Email: {process.env.BRAND_EMAIL}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<p className='text-sm text-gray-500'></p>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,15 +9,13 @@ export default function ConfirmationTemplate(code: string) {
|
||||
<Email
|
||||
title={'Welcome!'}
|
||||
body={
|
||||
<div className='mt-8'>
|
||||
<p className='text-base text-gray-700 dark:text-gray-400'>
|
||||
Dear subscriber,
|
||||
</p>
|
||||
<p className='mt-2 text-base text-gray-700 dark:text-gray-400'>
|
||||
<div className='text-base text-gray-700 dark:text-gray-400'>
|
||||
<p>Dear subscriber,</p>
|
||||
<p className='mt-2 '>
|
||||
thank you for subscribing to our newsletter! Please click the
|
||||
button below to confirm your subscription.
|
||||
</p>
|
||||
<div className='mt-8 flex justify-center'>
|
||||
<div className='my-8 flex justify-center'>
|
||||
<Link
|
||||
path={`${process.env.HOME_URL}/confirmation?code=${code}`}
|
||||
text='Confirm Subscription'
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { Container } from '@react-email/container';
|
||||
import { Html } from '@react-email/html';
|
||||
import { Section } from '@react-email/section';
|
||||
import { Text } from '@react-email/text';
|
||||
import { z } from 'zod';
|
||||
import { getRandomColor } from '../../utils/getRandomColor';
|
||||
import { NewsSchema } from '../../utils/schemas';
|
||||
import { Footer } from './components/footer';
|
||||
import Email from './template';
|
||||
|
||||
export default function NewsletterTemplate(
|
||||
stories: z.infer<typeof NewsSchema>[]
|
||||
@@ -26,36 +23,27 @@ export default function NewsletterTemplate(
|
||||
return {
|
||||
subject: `What's new from Hackernews?`,
|
||||
template: (
|
||||
<Html>
|
||||
<Section className='bg-white'>
|
||||
<div className='mx-auto w-full max-w-2xl overflow-hidden rounded-lg bg-white shadow-lg'>
|
||||
<div className='text-center '>
|
||||
<h1 className='my-4 text-3xl font-bold'>Good day!</h1>
|
||||
<p>
|
||||
<Email
|
||||
title='Good day!'
|
||||
body={
|
||||
<div className='text-base text-gray-700 dark:text-gray-400'>
|
||||
<p className='flex justify-center'>
|
||||
Here is something{' '}
|
||||
{sayings[Math.floor(Math.random() * sayings.length)]}:
|
||||
</p>
|
||||
</div>
|
||||
<Container
|
||||
style={{
|
||||
margin: '0 auto',
|
||||
padding: '20px 0 48px',
|
||||
width: '580px'
|
||||
}}
|
||||
>
|
||||
<Text>
|
||||
{stories.map(story => {
|
||||
const background = getRandomColor();
|
||||
|
||||
return (
|
||||
<div
|
||||
key={story.id}
|
||||
className='mt-8 rounded-lg border bg-card text-card-foreground shadow-sm'
|
||||
data-v0-t='card'
|
||||
style={{ backgroundColor: `${background}` }}
|
||||
>
|
||||
<div className='flex flex-col space-y-1.5 p-6'>
|
||||
<h2 className='text-2xl font-semibold'>
|
||||
{story.title}
|
||||
</h2>
|
||||
<p>{story.by}</p>
|
||||
<div className='flex flex-col space-y-1.5 px-6 pb-2 pt-6'>
|
||||
<h2 className='text-2xl font-semibold'>{story.title}</h2>
|
||||
<p className='italic'>by {story.by}</p>
|
||||
</div>
|
||||
{story.text && (
|
||||
<div className='px-6'>
|
||||
@@ -71,26 +59,16 @@ export default function NewsletterTemplate(
|
||||
</div>
|
||||
)}
|
||||
{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 className='p-6 text-right font-bold'>
|
||||
<a href={story.url}>Read more</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</Text>
|
||||
</Container>
|
||||
<Footer />
|
||||
</div>
|
||||
</Section>
|
||||
</Html>
|
||||
}
|
||||
/>
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Container } from '@react-email/container';
|
||||
import { Html } from '@react-email/html';
|
||||
import { Section } from '@react-email/section';
|
||||
import { Text } from '@react-email/text';
|
||||
import { getRandomColor } from '../../utils/getRandomColor';
|
||||
import { Footer } from './components/footer';
|
||||
|
||||
type EmailProps = {
|
||||
@@ -10,26 +9,18 @@ type EmailProps = {
|
||||
};
|
||||
|
||||
export default function Email({ title, body }: EmailProps) {
|
||||
const titleBackground = getRandomColor();
|
||||
|
||||
return (
|
||||
<Html>
|
||||
<Section className='mx-auto w-full max-w-2xl overflow-hidden rounded-lg bg-white shadow-lg'>
|
||||
<Container
|
||||
style={{
|
||||
margin: '0 auto',
|
||||
padding: '20px 0 48px',
|
||||
width: '580px'
|
||||
}}
|
||||
<Section className='max-w-2xl overflow-hidden rounded-lg bg-white shadow-lg'>
|
||||
<h1
|
||||
className='p-8 text-center text-3xl font-bold text-black'
|
||||
style={{ backgroundColor: `${titleBackground}` }}
|
||||
>
|
||||
<h1 className='mt-4 text-center text-3xl font-bold'>{title}</h1>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: '16px',
|
||||
marginBottom: '16px'
|
||||
}}
|
||||
>
|
||||
{body}
|
||||
</Text>
|
||||
</Container>
|
||||
{title}
|
||||
</h1>
|
||||
<div className='m-8 p-8'>{body}</div>
|
||||
<Footer />
|
||||
</Section>
|
||||
</Html>
|
||||
|
||||
@@ -9,8 +9,8 @@ export default function UnsubscribeTemplate() {
|
||||
<Email
|
||||
title="We're sad you're leaving :("
|
||||
body={
|
||||
<div className='mt-8'>
|
||||
<p className='mt-2 text-base text-gray-700 dark:text-gray-400'>
|
||||
<div className='text-base text-gray-700 dark:text-gray-400'>
|
||||
<p className='mt-2 '>
|
||||
You have been successfully unsubscribed from our newsletter. You
|
||||
won't receive any further communications from us unless you
|
||||
explicitly opt-in again.
|
||||
|
||||
8
utils/getRandomColor.ts
Normal file
8
utils/getRandomColor.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export function getRandomColor() {
|
||||
const letters = '6789ABCDEF';
|
||||
let color = '#';
|
||||
for (let i = 0; i < 6; i++) {
|
||||
color += letters[Math.floor(Math.random() * 10)];
|
||||
}
|
||||
return color;
|
||||
}
|
||||
Reference in New Issue
Block a user