Skip to content

Commit b45cba3

Browse files
committed
try send emails
1 parent d36989a commit b45cba3

File tree

11 files changed

+4987
-1044
lines changed

11 files changed

+4987
-1044
lines changed

convex/_generated/api.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type {
1414
FilterApi,
1515
FunctionReference,
1616
} from "convex/server";
17+
import type * as backoffice from "../backoffice.js";
1718
import type * as blocks from "../blocks.js";
1819
import type * as categories from "../categories.js";
1920
import type * as clerk from "../clerk.js";
@@ -42,6 +43,7 @@ import type * as utils from "../utils.js";
4243
* ```
4344
*/
4445
declare const fullApi: ApiFromModules<{
46+
backoffice: typeof backoffice;
4547
blocks: typeof blocks;
4648
categories: typeof categories;
4749
clerk: typeof clerk;

convex/backoffice.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { adminAuthAction } from '~/convex/utils'
2+
import { internal } from '~/convex/_generated/api'
3+
import { v } from 'convex/values'
4+
5+
export const testSendEmail = adminAuthAction({
6+
args: {
7+
token: v.string()
8+
},
9+
handler: async ({ runAction }, { token }) => {
10+
await runAction(internal.email.sendEmailToUser, {
11+
token,
12+
13+
14+
subject: 'Test suggestionApprovedEmail Email',
15+
type: 'suggestionApprovedEmail',
16+
data: {
17+
suggestion: '',
18+
type: '',
19+
points: 0
20+
}
21+
})
22+
}
23+
})

convex/email.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const resend = new Resend(process.env.RESEND_API_KEY)
99

1010
export const sendEmail = internalAction({
1111
args: { subject: v.string(), html: v.string() },
12-
handler: async (_, { subject, html }) => {
12+
handler: async ({ auth }, { subject, html }) => {
1313
return resend.emails.send({
1414
1515
@@ -18,3 +18,30 @@ export const sendEmail = internalAction({
1818
})
1919
}
2020
})
21+
22+
export const sendEmailToUser = internalAction({
23+
args: {
24+
from: v.string(),
25+
to: v.string(),
26+
subject: v.string(),
27+
type: v.union(
28+
v.literal('suggestionApprovedEmail'),
29+
v.literal('suggestionRejectedEmail'),
30+
v.literal('feedbackReceivedEmail'),
31+
v.literal('feedbackReplyEmail'),
32+
v.literal('promotionEmail')
33+
),
34+
data: v.any(),
35+
token: v.string()
36+
},
37+
handler: async (_, payload) => {
38+
const res = fetch('https://stacks.hackazen.com/api/sendEmail', {
39+
method: 'POST',
40+
headers: {
41+
'Content-Type': 'application/json',
42+
Authorization: `Bearer ${payload.token}`
43+
},
44+
body: JSON.stringify(payload)
45+
})
46+
}
47+
})

convex/suggestions.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,13 @@ export const internalApproveSuggestion = internalMutation({
8484

8585
export const approveSuggestion = adminAuthAction({
8686
args: {
87-
suggestionId: v.id('suggestions')
87+
suggestionId: v.id('suggestions'),
88+
token: v.union(v.string(), v.null())
8889
},
89-
handler: async ({ runQuery, runMutation, runAction }, { suggestionId }) => {
90+
handler: async (
91+
{ runQuery, runMutation, runAction },
92+
{ suggestionId, token }
93+
) => {
9094
const suggestion = await runQuery(internal.suggestions.getSuggestionById, {
9195
suggestionId
9296
})
@@ -151,12 +155,23 @@ export const approveSuggestion = adminAuthAction({
151155
type: 'suggestionApproved'
152156
})
153157
}
154-
if (userSettings?.suggestionApprovedEmail) {
155-
// Todo: send email
156-
// await runAction(internal.email.sendEmail, {
157-
// subject: 'Suggestion Approved',
158-
// html: `<p>Your suggestion for <b>${suggestion.name}</b> has been approved.</p>`
159-
// })
158+
if (userSettings?.suggestionApprovedEmail && token) {
159+
const user = await runQuery(internal.users.getUserById, {
160+
userId: suggestion.userId
161+
})
162+
if (!user) return true
163+
await runAction(internal.email.sendEmailToUser, {
164+
subject: 'Suggestion Approved',
165+
166+
to: user.email,
167+
type: 'suggestionApprovedEmail',
168+
data: {
169+
suggestion: suggestion.name,
170+
type: suggestion.type,
171+
points: pointsPerSuggestionType[suggestion.type]
172+
},
173+
token
174+
})
160175
}
161176
}
162177

emails/SuggestionApprovedEmail.tsx

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import {
2+
Body,
3+
Button,
4+
Container,
5+
Head,
6+
Hr,
7+
Html,
8+
Link,
9+
Preview,
10+
Section,
11+
Text
12+
} from 'jsx-email'
13+
// Note: Superstruct is a fantastic validation package. It's smaller and faster than alternatives
14+
// and uses a delightful API without chaining. docs.superstructjs.org
15+
//
16+
// To install `superstruct` run `pnpm add superstruct`.
17+
18+
import * as React from 'react'
19+
20+
export const TemplateName = 'BatmanEmail'
21+
22+
const main = {
23+
backgroundColor: '#f6f9fc',
24+
fontFamily:
25+
'-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif'
26+
}
27+
28+
const container = {
29+
backgroundColor: '#ffffff',
30+
margin: '0 auto',
31+
marginBottom: '64px',
32+
padding: '20px 0 48px'
33+
}
34+
35+
const box = {
36+
padding: '0 48px'
37+
}
38+
39+
const hr = {
40+
borderColor: '#e6ebf1',
41+
margin: '20px 0'
42+
}
43+
44+
const paragraph = {
45+
color: '#777',
46+
fontSize: '16px',
47+
lineHeight: '24px',
48+
textAlign: 'left' as const
49+
}
50+
51+
const anchor = {
52+
color: '#777'
53+
}
54+
55+
const button = {
56+
backgroundColor: '#777',
57+
borderRadius: '5px',
58+
color: '#fff',
59+
display: 'block',
60+
fontSize: '16px',
61+
fontWeight: 'bold',
62+
textAlign: 'center' as const,
63+
textDecoration: 'none',
64+
width: '100%',
65+
padding: '10px'
66+
}
67+
68+
export const SuggestionApprovedEmail = ({
69+
email,
70+
name
71+
}: { email: string; name: string }) => (
72+
<Html>
73+
<Head />
74+
<Preview>
75+
This is our email preview text for {name} &lt;{email}&gt;
76+
</Preview>
77+
<Body style={main}>
78+
<Container style={container}>
79+
<Section style={box}>
80+
<Text style={paragraph}>This is our email body text</Text>
81+
<Button style={button} href="https://example.com">
82+
Action Button
83+
</Button>
84+
<Hr style={hr} />
85+
<Text style={paragraph}>
86+
This is text content with a{' '}
87+
<Link style={anchor} href="mailto:{email}">
88+
link
89+
</Link>
90+
.
91+
</Text>
92+
</Section>
93+
</Container>
94+
</Body>
95+
</Html>
96+
)

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
"friendly-username-generator": "^2.0.4",
2525
"html-to-image": "^1.11.11",
2626
"imagekit": "^5.0.0",
27+
"jsonwebtoken": "^9.0.2",
28+
"jsx-email": "^1.10.12",
2729
"lucide-react": "^0.344.0",
2830
"next": "14.1.0",
2931
"next-themes": "^0.2.1",
@@ -45,6 +47,7 @@
4547
},
4648
"devDependencies": {
4749
"@biomejs/biome": "1.5.3",
50+
"@types/jsonwebtoken": "^9.0.6",
4851
"@types/node": "^20",
4952
"@types/react": "^18",
5053
"@types/react-dom": "^18",

0 commit comments

Comments
 (0)