Skip to content

Commit 47587f9

Browse files
authored
Merge pull request #143 from CodeForStartup/feat/send-mail
feat: config mail send
2 parents 7223672 + 9f37da6 commit 47587f9

File tree

27 files changed

+1402
-340
lines changed

27 files changed

+1402
-340
lines changed

README.md

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,26 +33,26 @@ turbo dev
3333

3434
# Libraries
3535

36-
- ReactJS
37-
- TypeScript
38-
- NextJS 14 - App router and server actions
39-
- next-auth
40-
- Prisma ORM
41-
- Postgres
42-
- Turborepo
43-
- TailwindCSS
44-
- shadcn
45-
- next-themes
46-
- Zod validation
36+
- ReactJS
37+
- TypeScript
38+
- NextJS 14 - App router and server actions
39+
- next-auth
40+
- Prisma ORM
41+
- Postgres
42+
- Turborepo
43+
- TailwindCSS
44+
- shadcn
45+
- next-themes
46+
- Zod validation
4747
- React Form Hook
48-
- Tsup
49-
- EditorJs
50-
- react-toastify
51-
- react-textarea-autosize
52-
- lucide-react icon
53-
- dayjs
54-
- Eslint
55-
- Husky
48+
- Tsup
49+
- EditorJs
50+
- react-toastify
51+
- react-textarea-autosize
52+
- lucide-react icon
53+
- dayjs
54+
- Eslint
55+
- Husky
5656
- Prettier
5757

5858
# Functions

apps/admin/@/molecules/nav/search-bar.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { useEffect, useRef, useState } from "react"
44
import { useRouter, useSearchParams } from "next/navigation"
55

6+
import { X } from "lucide-react"
67
import { useTranslations } from "next-intl"
78

89
import { Input } from "@/components/ui/input"
@@ -80,7 +81,7 @@ export default function SearchBar() {
8081
variant="outline"
8182
onClick={onClear}
8283
>
83-
<i className="ri-close-line text-[20px]" />
84+
<X />
8485
</Button>
8586
<Button
8687
variant="default"

apps/admin/@/molecules/user/posts/post-meta.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
import Link from "next/link"
33

44
import dayjs from "dayjs"
5+
import { Avatar, AvatarFallback, AvatarImage } from "ui"
56

6-
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
77
import { TPostItem } from "@/types/posts"
88

99
export type PostMetaProps = {

apps/web/.env.example

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
NEXTAUTH_SECRET=
2+
NEXTAUTH_URL=
3+
4+
NEXT_PUBLIC_FRONTEND_URL=
5+
6+
GITHUB_ID=
7+
GITHUB_SECRET=
8+
9+
JWT_SECRET=
10+
11+
NEXT_PUBLIC_FB_APP_ID=
12+
13+
RESEND_AUDIENCE_ID=

apps/web/@/actions/auth/index.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
"use server"
22

3-
import bcrypt from "bcrypt"
3+
import bcryptjs from "bcryptjs"
44
import { signIn, signOut } from "configs/auth"
55
import { Prisma } from "database"
66
import { createUser } from "database/src/users/queries"
7+
import { sendEmail } from "emails"
8+
import VerifyEmail from "emails/verify-email"
79

810
import { redirect } from "@/utils/navigation"
911

@@ -33,15 +35,40 @@ export const signUp = async (
3335
try {
3436
// hash password
3537
const { email, password } = data
36-
const hashedPassword = await bcrypt.hash(password, 10)
38+
const hashedPassword = await bcryptjs.hash(password, 10)
3739

3840
await createUser({
3941
data: {
4042
email,
4143
password: hashedPassword,
4244
},
4345
})
46+
47+
console.log("create user...successfully")
48+
49+
// create verification code
50+
const token = crypto.randomUUID()
51+
await prisma.verificationToken.create({
52+
data: {
53+
token, // 6 digits code
54+
identifier: email,
55+
expires: new Date(Date.now() + 1000 * 60 * 60 * 24), // 1 day
56+
},
57+
})
58+
59+
console.log("verification token...successfully")
60+
61+
// send email
62+
await sendEmail({
63+
email,
64+
subject: "Welcome to Next Forum",
65+
react: VerifyEmail({
66+
token,
67+
email,
68+
}),
69+
})
4470
} catch (error) {
71+
console.error("signUp.error", error)
4572
if (error?.error?.code === "P2002") {
4673
return {
4774
formErrors: null,

apps/web/@/libs/resend/index.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { Resend } from "resend"
2+
3+
const resendApiKey = process.env.RESEND_API_KEY
4+
5+
export const resend = resendApiKey ? new Resend(resendApiKey) : null
6+
7+
export async function subscribe({ email, name }: { email: string; name?: string | null }) {
8+
const audienceId = process.env.RESEND_AUDIENCE_ID
9+
10+
if (!audienceId) {
11+
console.error("RESEND_AUDIENCE_ID is not set in the .env. Skipping.")
12+
return
13+
}
14+
15+
return await resend?.contacts.create({
16+
email,
17+
...(name && {
18+
firstName: name.split(" ")[0],
19+
lastName: name.split(" ").slice(1).join(" "),
20+
}),
21+
audienceId: process.env.RESEND_AUDIENCE_ID as string,
22+
})
23+
}
24+
25+
export async function unsubscribe({ email }: { email: string }) {
26+
return await resend?.contacts.remove({
27+
email,
28+
audienceId: process.env.RESEND_AUDIENCE_ID as string,
29+
})
30+
}

apps/web/@/molecules/auth/sign-up/index.tsx

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,18 @@
33
import Link from "next/link"
44

55
import { zodResolver } from "@hookform/resolvers/zod"
6-
import { Prisma } from "database"
7-
import { GithubIcon } from "lucide-react"
86
import { useTranslations } from "next-intl"
9-
import { useFormState } from "react-dom"
107
import { useForm } from "react-hook-form"
118
import { toast } from "react-toastify"
129
import {
1310
Button,
14-
Card,
15-
CardContent,
16-
CardFooter,
1711
Form,
1812
FormControl,
1913
FormField,
2014
FormItem,
2115
FormLabel,
2216
FormMessage,
2317
Input,
24-
Label,
2518
Typography,
2619
} from "ui"
2720
import { z } from "zod"

apps/web/@/molecules/footer/theme-toggle.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import React from "react"
44

5+
import { MonitorDot, Moon, Sun } from "lucide-react"
56
import { useTheme } from "next-themes"
67
import { Button } from "ui"
78

@@ -19,21 +20,21 @@ const ThemeToggle: React.FC = () => {
1920
variant="link"
2021
className="hover:no-underline"
2122
>
22-
<i className="ri-mac-line" />
23+
<MonitorDot />
2324
</Button>
2425
<Button
2526
onClick={toggleTheme}
2627
variant="link"
2728
className="hover:no-underline"
2829
>
29-
<i className="ri-sun-line" />
30+
<Sun />
3031
</Button>
3132
<Button
3233
onClick={toggleTheme}
3334
variant="link"
3435
className="hover:no-underline"
3536
>
36-
<i className="ri-moon-line" />
37+
<Moon />
3738
</Button>
3839
</div>
3940
)

apps/web/@/molecules/nav/search-bar.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
import { useEffect, useRef, useState } from "react"
44
import { useRouter, useSearchParams } from "next/navigation"
55

6+
import { SquareX, X } from "lucide-react"
67
import { useTranslations } from "next-intl"
7-
import { Button, cn, Input } from "ui"
8+
import { Button, Input } from "ui"
89

910
export default function SearchBar() {
1011
const t = useTranslations()
@@ -71,13 +72,16 @@ export default function SearchBar() {
7172
<div className="absolute right-1 top-1 flex h-8 items-center">
7273
{searchTerm && (
7374
<>
74-
<Button
75-
className="h-8 w-8 border-none hover:bg-transparent"
76-
variant="outline"
75+
<button
76+
className="h-8 w-8 border-none"
7777
onClick={onClear}
7878
>
79-
<i className="ri-close-line text-[20px]" />
80-
</Button>
79+
<X
80+
size={20}
81+
strokeWidth={2}
82+
color="black"
83+
/>
84+
</button>
8185
<Button
8286
variant="default"
8387
onClick={onSearch}

apps/web/@/molecules/posts/post-detail/like-button/LikeButton.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use client"
22

33
import { PostOnUserType, TPostItem } from "database"
4+
import { Heart } from "lucide-react"
45
import { useTranslations } from "next-intl"
56
import { Button, cn, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "ui"
67

@@ -35,11 +36,9 @@ const LikeButton: React.FC<LikeButtonProps> = ({ children, post, isLiked }: Like
3536
"hover:border-stale-300 border-stale-800 flex h-12 w-12 items-center justify-center rounded-full border bg-white p-0 text-2xl hover:bg-slate-200"
3637
)}
3738
>
38-
<i
39-
className={cn("ri-heart-3-fill", {
40-
"text-red-500": isLiked,
41-
"text-gray-500 ": !isLiked,
42-
})}
39+
<Heart
40+
fill={isLiked ? "tomato" : "gray"}
41+
color={isLiked ? "tomato" : "gray"}
4342
/>
4443
</Button>
4544
</TooltipTrigger>

0 commit comments

Comments
 (0)