Skip to content

Commit

Permalink
feat: impl create newsletter
Browse files Browse the repository at this point in the history
  • Loading branch information
CaliCastle committed Jun 15, 2023
1 parent b645254 commit cf532fb
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 18 deletions.
1 change: 1 addition & 0 deletions app/admin/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export function Sidebar() {
{navigation.map((item) => (
<li key={item.name}>
<Link
prefetch={false}
href={`/admin${item.href}`}
className={clsxm(
isActive(item.href)
Expand Down
78 changes: 71 additions & 7 deletions app/admin/newsletters/new/page.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,77 @@
import { Card, Text, TextInput, Title } from '@tremor/react'
import { extendDateTime, parseDateTime } from '@zolplay/utils'
import { lte } from 'drizzle-orm'
import { redirect } from 'next/navigation'
import { z } from 'zod'

import { Button } from '~/components/ui/Button'
import { emailConfig } from '~/config/email'
import { db } from '~/db'
import { newsletters, subscribers } from '~/db/schema'
import NewslettersTemplate from '~/emails/NewslettersTemplate'
import { resend } from '~/lib/mail'

extendDateTime({
timezone: true,
})

const CreateNewsletterSchema = z.object({
subject: z.string().nonempty(),
body: z.string().nonempty(),
})
export default function CreateNewsletterPage() {
async function addNewsletter(formData: FormData) {
'use server'

const data = CreateNewsletterSchema.parse(
Object.fromEntries(formData.entries())
)

const subs = await db
.select({
email: subscribers.email,
})
.from(subscribers)
.where(lte(subscribers.subscribedAt, new Date()))
const subscriberEmails = new Set([
...subs
.filter((sub) => typeof sub.email === 'string' && sub.email.length > 0)
.map((sub) => sub.email!),
])

await resend.sendEmail({
subject: data.subject,
from: emailConfig.from,
to: Array.from(subscriberEmails),
react: NewslettersTemplate({
subject: data.subject,
body: data.body,
}),
})

await db.insert(newsletters).values({
...data,
sentAt: parseDateTime({
date: new Date(),
timezone: 'Asia/Shanghai',
})?.toDate(),
})

redirect('/admin/newsletters')
}

return (
<>
<Title>Create a newsletter</Title>

<Card className="mt-6">
<form>
<form className="flex flex-col gap-4" action={addNewsletter}>
<div className="flex flex-col space-y-3">
<Text>Subject</Text>
<TextInput name="subject" required />
</div>
<div className="flex flex-col space-y-3">
<Text>Sent at</Text>
<input type="datetime-local" name="sent_at" required />
</div>
<div className="flex flex-col space-y-3">

<div className="flex flex-col space-y-2">
<label
htmlFor="body"
className="block text-sm font-medium leading-6 text-zinc-800 dark:text-zinc-200"
Expand All @@ -27,11 +83,19 @@ export default function CreateNewsletterPage() {
rows={20}
name="body"
id="body"
className="block w-full rounded-md border-0 py-1.5 text-zinc-900 shadow-sm ring-1 ring-inset ring-zinc-300 placeholder:text-zinc-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 dark:text-zinc-100 dark:ring-zinc-700 sm:text-sm sm:leading-6"
className="block w-full rounded-md border-0 py-1.5 text-zinc-900 shadow-sm ring-1 ring-inset ring-zinc-300 placeholder:text-zinc-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 dark:bg-slate-800 dark:text-zinc-100 dark:ring-zinc-700 sm:text-sm sm:leading-6"
defaultValue={''}
/>
</div>
</div>

<footer className="border-t border-gray-900/10 pt-6 dark:border-gray-100/10">
<div className="flex justify-end">
<Button type="submit" className="">
Create
</Button>
</div>
</footer>
</form>
</Card>
</>
Expand Down
2 changes: 1 addition & 1 deletion emails/ConfirmSubscription.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react'

import { emailConfig } from '../config/email'
import { Button, Heading, Hr, Img, Link, Section, Text } from './_components'
import { Layout } from './Layout'
import Layout from './Layout'

const ConfirmSubscriptionEmail = ({ link = 'link.com/confirm?fake-token' }) => {
const previewText = `确认订阅 Cali 的动态吗?`
Expand Down
4 changes: 2 additions & 2 deletions emails/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
Text,
} from './_components'

export function Layout({
export default function Layout({
previewText,
children,
}: {
Expand All @@ -28,7 +28,7 @@ export function Layout({
<Preview>{previewText}</Preview>
<Tailwind>
<Body className="mx-auto my-auto bg-zinc-50 pt-[32px] font-sans">
<Container className="mx-auto my-[40px] w-[465px] rounded-2xl border border-solid border-zinc-100 bg-white px-[24px] py-[20px]">
<Container className="mx-auto my-[40px] w-[465px] max-w-[465px] rounded-2xl border border-solid border-zinc-100 bg-white px-[24px] py-[20px]">
{children}
</Container>

Expand Down
2 changes: 1 addition & 1 deletion emails/NewGuestbook.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import ReactMarkdown from 'react-markdown'

import { parseDisplayName } from '../lib/string'
import { Button, Heading, Hr, Img, Section, Text } from './_components'
import { Layout } from './Layout'
import Layout from './Layout'

const NewGuestbookEmail = ({
link = 'https://caliso/guestbook',
Expand Down
2 changes: 1 addition & 1 deletion emails/NewReplyComment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import ReactMarkdown from 'react-markdown'

import { parseDisplayName } from '../lib/string'
import { Button, Heading, Hr, Img, Link, Section, Text } from './_components'
import { Layout } from './Layout'
import Layout from './Layout'

const NewReplyCommentEmail = ({
postLink = 'https://cali.so',
Expand Down
25 changes: 19 additions & 6 deletions emails/NewslettersTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@ import * as React from 'react'
import ReactMarkdown from 'react-markdown'

import { Heading, Section } from './_components'
import { Layout } from './Layout'
import Layout from './Layout'

const NewslettersTemplate = (props: {
subject?: string | null
body?: string | null
}) => {
const {
subject = '测试主题',
body = `# 你好,世界
- 你好
- 世界
body = `## 最近的一些更新与变化
![](https://zolplay.com/api/og?title=Some+Recent+Changes)
`,
} = props

Expand All @@ -21,8 +20,22 @@ const NewslettersTemplate = (props: {
<Heading>{subject}</Heading>

{body && (
<Section className="px-2 text-[14px] leading-[16px] text-zinc-700">
<ReactMarkdown>{body}</ReactMarkdown>
<Section className="max-w-[465px] px-2 text-[14px] leading-[16px] text-zinc-700">
<ReactMarkdown
components={{
img: ({ src, alt }) => {
return (
<img
src={src}
alt={alt}
className="mx-auto my-0 max-w-[420px]"
/>
)
},
}}
>
{body}
</ReactMarkdown>
</Section>
)}
</Layout>
Expand Down

0 comments on commit cf532fb

Please sign in to comment.