├── .eslintrc.json ├── .gitignore ├── README.md ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── prisma └── schema.prisma ├── public └── og-image.png ├── src ├── app │ ├── admin │ │ └── page.tsx │ ├── api │ │ ├── emails │ │ │ └── route.ts │ │ └── subscribe │ │ │ └── route.ts │ ├── component │ │ ├── EmailTemplates.tsx │ │ ├── InputForm.tsx │ │ ├── MemberJoined.tsx │ │ ├── MyEmails.tsx │ │ └── SnowfalBG.tsx │ ├── favicon.ico │ ├── globals.css │ ├── layout.tsx │ └── page.tsx └── lib │ ├── EmailValidator.ts │ ├── Prisma.db.ts │ └── SendingEmail.ts ├── tailwind.config.ts └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | .env 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Devletter - Newsletter for Developer's 2 | 3 | A beautiful Newletter website made with Nextjs. 4 | 5 | ![Demo] (https://firebasestorage.googleapis.com/v0/b/projectfriendz-45b49.appspot.com/o/images%2Fbanner2.png?alt=media&token=c96ca45b-6abf-4ff9-972d-217d89ab4d65) 6 | 7 | The site is implemented with beautiful template of thankyou email for the user who join.. But now in live it would not send you welcome email because it is hosted on vercel and render required verified domains.. 8 | 9 | So to test thankyou email feature, clone it and don't forget to add your `RESEND_API_KEY` in .env file 10 | 11 | ### Tech stack : 12 | 1. Nextjs, 13 | 2. Typescript, 14 | 3. Tailwindscc 15 | 4. serverAction 16 | 5. ReactForm 17 | 6. Render (for email) 18 | 7. React-email (for email template) 19 | 8. ... 20 | 21 | ## Clone it with few steps 22 | 23 | First, run the development server: 24 | 25 | ```bash 26 | npm run dev 27 | # or 28 | yarn dev 29 | # or 30 | pnpm dev 31 | # or 32 | bun dev 33 | ``` 34 | 35 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 36 | 37 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 38 | 39 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 40 | 41 | ## Learn More 42 | 43 | To learn more about Next.js, take a look at the following resources: 44 | 45 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 46 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 47 | 48 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 49 | 50 | ## Deploy on Vercel 51 | 52 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 53 | 54 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 55 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {} 3 | 4 | module.exports = nextConfig 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "postinstall": "prisma generate" 11 | }, 12 | "dependencies": { 13 | "@emailjs/browser": "^3.11.0", 14 | "@hookform/resolvers": "^3.3.2", 15 | "@prisma/client": "^5.6.0", 16 | "@react-email/components": "^0.0.11", 17 | "@react-email/tailwind": "^0.0.12", 18 | "axios": "^1.6.2", 19 | "lucide-react": "^0.292.0", 20 | "next": "14.0.3", 21 | "prisma": "^5.6.0", 22 | "react": "^18", 23 | "react-dom": "^18", 24 | "react-hook-form": "^7.48.2", 25 | "react-snowfall": "^1.2.1", 26 | "resend": "^2.0.0", 27 | "sonner": "^1.2.2", 28 | "zod": "^3.22.4" 29 | }, 30 | "devDependencies": { 31 | "@types/node": "^20", 32 | "@types/react": "^18", 33 | "@types/react-dom": "^18", 34 | "autoprefixer": "^10.0.1", 35 | "eslint": "^8", 36 | "eslint-config-next": "14.0.3", 37 | "postcss": "^8", 38 | "tailwindcss": "^3.3.0", 39 | "typescript": "^5" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | generator client { 2 | provider = "prisma-client-js" 3 | } 4 | 5 | datasource db { 6 | provider = "mongodb" 7 | url = env("DATABASE_URL") 8 | } 9 | 10 | 11 | model subscriber { 12 | id String @id @default(auto()) @map("_id") @db.ObjectId 13 | name String? 14 | email String 15 | createdAt DateTime @default(now()) 16 | } -------------------------------------------------------------------------------- /public/og-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taqui-786/Devletter/dc30c504841627470f4b6de483f18deb09afcb73/public/og-image.png -------------------------------------------------------------------------------- /src/app/admin/page.tsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | import MyEmails from "../component/MyEmails"; 4 | 5 | const adminPage = () => { 6 | 7 | return ( 8 |
9 | 10 |
11 | ); 12 | }; 13 | 14 | export default adminPage; 15 | -------------------------------------------------------------------------------- /src/app/api/emails/route.ts: -------------------------------------------------------------------------------- 1 | import { db } from "@/lib/Prisma.db" 2 | import { NextResponse } from "next/server" 3 | 4 | 5 | 6 | export async function GET (req:Request){ 7 | try { 8 | const data = await db.subscriber.findMany() 9 | return NextResponse.json({data}, {status:200}) 10 | } catch (error) { 11 | return new Response("Something wrong !", {status:500}) 12 | } 13 | } -------------------------------------------------------------------------------- /src/app/api/subscribe/route.ts: -------------------------------------------------------------------------------- 1 | import { db } from "@/lib/Prisma.db"; 2 | 3 | export async function POST(req: Request) { 4 | try { 5 | const body = await req.json(); 6 | 7 | const existed = await db.subscriber.findFirst({ 8 | where: { 9 | email: body.email, 10 | }, 11 | }); 12 | // CHECKING EMAIL given is already exist or Not 13 | if (!body.email) { 14 | return new Response("Action Prohibited!", { status: 301 }); 15 | } 16 | // CHECKING EMAIL EXIST FOR NOT 17 | if (existed) { 18 | return new Response("Member Already Existed", { status: 201 }); 19 | } 20 | // GETTING USERNAME FROM EMAL 21 | var str = body.email; 22 | var nameParts = str.split("@"); 23 | var username = nameParts.length == 2 ? nameParts[0] : null; 24 | // CREATING SUBSCRIBER 25 | await db.subscriber.create({ 26 | data: { 27 | name: username, 28 | email: body.email, 29 | }, 30 | }); 31 | return new Response("Subscribed to Devletter", { status: 200 }); 32 | } catch (error) { 33 | return new Response("Could not like.. Please try later" + error, { 34 | status: 500, 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/app/component/EmailTemplates.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Column, 4 | Container, 5 | Head, 6 | Heading, 7 | Hr, 8 | Html, 9 | Img, 10 | Link, 11 | Preview, 12 | Section, 13 | Text, 14 | Row, 15 | } from "@react-email/components"; 16 | import * as React from "react"; 17 | 18 | interface ThankyouEmailProps { 19 | tips?: { id: number; description: string }[]; 20 | } 21 | 22 | const baseUrl = process.env.VERCEL_URL 23 | ? `https://${process.env.VERCEL_URL}` 24 | : ""; 25 | 26 | const PropDefaults: ThankyouEmailProps = { 27 | tips: [ 28 | { 29 | id: 1, 30 | description: 31 | "📰 Weekly Tech News: Stay in the loop with bite-sized updates on the latest tech trends.", 32 | }, 33 | { 34 | id: 1, 35 | description: 36 | "💡 Amazing Projects: Check out mind-blowing projects and get inspired.", 37 | }, 38 | { 39 | id: 1, 40 | description: 41 | "📚 Valuable Blogs and Tips: Level up your tech game with tips that even your younger self would understand.", 42 | }, 43 | { 44 | id: 1, 45 | description: 46 | "🚀 First Dibs on Hackathons: Be the first to know about hackathons and show off your skills.", 47 | }, 48 | ], 49 | }; 50 | 51 | export const ThankyouEmail = ({ 52 | tips = PropDefaults.tips, 53 | }: ThankyouEmailProps) => ( 54 | 55 | 56 | Thankyou for joining us. 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 |
68 | 69 | Guess what? 70 | 71 | 72 | You just made one awesome decision by joining DevLetter! 73 | 🎉 Get ready for a tech-packed journey filled with weekly news, cool 74 | projects, expert tips, and exclusive hackathon invites. 75 | 76 | 77 |
78 | 79 | 80 | Here's the lowdown on what you're in for: 81 | 82 | 83 | 90 | 91 | 92 | Once again, thank you for choosing DevLetter. Together, let's make 93 | this community a hub of innovation and collaboration. 94 | 95 | 96 |
97 | 98 | 99 | Feel free to reach out, share your thoughts, or suggest topics you'd like to see covered in our newsletters. 100 | 101 |
102 | 103 | Share your idea 104 | 105 |
106 | 107 |
108 |
109 | 110 |
111 | 112 | You're receiving this email because your Subscried devletter. 113 | 114 | 115 | 116 | Unsubscribe from emails like this{" "} 117 | 118 | 119 | Edit email settings{" "} 120 | 121 | 122 | Contact us 123 | 124 | 125 | Privacy 126 | 127 | 128 |
129 | 130 | 131 | 132 | Devletter, Weeklky newsletter for devs. 133 | 134 | {"💌"} 135 |
136 | 137 | 138 | ); 139 | 140 | export default ThankyouEmail; 141 | 142 | const main = { 143 | backgroundColor: "#f3f3f5", 144 | fontFamily: "HelveticaNeue,Helvetica,Arial,sans-serif", 145 | }; 146 | 147 | const headerContent = { padding: "20px 30px 15px" }; 148 | 149 | const headerContentTitle = { 150 | color: "#fff", 151 | fontSize: "27px", 152 | fontWeight: "bold", 153 | lineHeight: "27px", 154 | }; 155 | 156 | 157 | 158 | const headerImageContainer = { 159 | padding: "30px 10px", 160 | }; 161 | 162 | const title = { 163 | margin: "0 0 15px", 164 | fontWeight: "bold", 165 | fontSize: "21px", 166 | lineHeight: "21px", 167 | color: "#0c0d0e", 168 | }; 169 | 170 | const paragraph = { 171 | fontSize: "15px", 172 | lineHeight: "21px", 173 | color: "#3c3f44", 174 | }; 175 | 176 | const divider = { 177 | margin: "30px 0", 178 | }; 179 | 180 | const container = { 181 | maxWidth: "680px", 182 | width: "100%", 183 | margin: "0 auto", 184 | backgroundColor: "#ffffff", 185 | }; 186 | 187 | const footer = { 188 | width: "680px", 189 | margin: "32px auto 0 auto", 190 | padding: "0 30px", 191 | }; 192 | 193 | const content = { 194 | padding: "30px 30px 40px 30px", 195 | }; 196 | 197 | const logo = { 198 | display: "flex", 199 | background: "#f3f3f5", 200 | padding: "20px 30px", 201 | }; 202 | 203 | const header = { 204 | borderRadius: "5px 5px 0 0", 205 | display: "flex", 206 | flexDireciont: "column", 207 | backgroundColor: "#030712", 208 | }; 209 | 210 | const buttonContainer = { 211 | marginTop: "24px", 212 | display: "block", 213 | }; 214 | 215 | const button = { 216 | backgroundColor: "#030712", 217 | border: "1px solid #0077cc", 218 | fontSize: "17px", 219 | lineHeight: "17px", 220 | padding: "13px 17px", 221 | borderRadius: "4px", 222 | maxWidth: "120px", 223 | color: "#fff", 224 | }; 225 | 226 | const footerDivider = { 227 | ...divider, 228 | borderColor: "#d6d8db", 229 | }; 230 | 231 | const footerText = { 232 | fontSize: "12px", 233 | lineHeight: "15px", 234 | color: "#9199a1", 235 | margin: "0", 236 | }; 237 | 238 | const footerLink = { 239 | display: "inline-block", 240 | color: "#9199a1", 241 | textDecoration: "underline", 242 | fontSize: "12px", 243 | marginRight: "10px", 244 | marginBottom: "0", 245 | marginTop: "8px", 246 | }; 247 | 248 | const footerAddress = { 249 | margin: "4px 0", 250 | fontSize: "12px", 251 | lineHeight: "15px", 252 | color: "#9199a1", 253 | }; 254 | 255 | const footerHeart = { 256 | borderRadius: "1px", 257 | border: "1px solid #d6d9dc", 258 | padding: "4px 6px 3px 6px", 259 | fontSize: "11px", 260 | lineHeight: "11px", 261 | fontFamily: "Consolas,monospace", 262 | color: "#e06c77", 263 | maxWidth: "min-content", 264 | margin: "0 0 32px 0", 265 | }; 266 | -------------------------------------------------------------------------------- /src/app/component/InputForm.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { EmailSchema } from "@/lib/EmailValidator"; 3 | import { SendEmail } from "@/lib/SendingEmail"; 4 | import { zodResolver } from "@hookform/resolvers/zod"; 5 | import axios from "axios"; 6 | import { Loader2, MailCheck } from "lucide-react"; 7 | import { useRef, useState } from "react"; 8 | import { SubmitHandler, useForm } from "react-hook-form"; 9 | import { z } from "zod"; 10 | import { toast } from "sonner"; 11 | import emailjs from '@emailjs/browser' 12 | export type EmailFormInput = z.infer; 13 | const InputForm = () => { 14 | const [submitted, setSubmitted] = useState(false); 15 | const emailRef:any = useRef(null) 16 | const { 17 | register, 18 | handleSubmit, 19 | reset, 20 | formState: { errors, isSubmitting }, 21 | } = useForm({ 22 | resolver: zodResolver(EmailSchema), 23 | }); 24 | const sendMailwithResend = async (val: any) => { 25 | const result = await SendEmail(val); 26 | console.log(result); 27 | if (result?.success === false) { 28 | toast.error("Email is invalid."); 29 | } 30 | }; 31 | const processForm: SubmitHandler = async (data) => { 32 | const addToDb = await axios.post("/api/subscribe", data); 33 | if (addToDb.status === 200) { 34 | await emailjs.sendForm("service_b9il39c","template_k2r7rye", emailRef.current ,'kxJ8hgjFVVPIIG5ar') 35 | // sendMailwithResend(data); require resend subscription 36 | 37 | reset(); 38 | setSubmitted(true); 39 | } else if (addToDb.status === 201) { 40 | toast.info("Member already subscribed"); 41 | reset(); 42 | } else { 43 | toast.error("Something went wrong"); 44 | } 45 | }; 46 | 47 | return ( 48 | <> 49 | {submitted ? ( 50 |
51 | 52 | 53 | Subscribed Devletter Successfully. 54 | 55 |
56 | ) : ( 57 | <> 58 |
62 | 70 | 81 |
82 | 83 | )} 84 | 85 | ); 86 | }; 87 | 88 | export default InputForm; 89 | -------------------------------------------------------------------------------- /src/app/component/MemberJoined.tsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | const MemberJoined = () => { 4 | 5 | 6 | return( 7 | 8 | 9 | 10 | 11 | 12 | 13 | 2k+ Member joined 14 | 15 | 16 | ) 17 | } 18 | 19 | export default MemberJoined -------------------------------------------------------------------------------- /src/app/component/MyEmails.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import axios from "axios"; 4 | import { useState } from "react"; 5 | export const dynamic = "force-dynamic"; 6 | const MyEmails = () => { 7 | const [click, setClick] = useState("Get email quantity"); 8 | const getEmails = async () => { 9 | try { 10 | setClick('🔅Loading...') 11 | const data = await axios.get("/api/emails",{ headers:{'Cache-Control': 'no-store'} }); 12 | const emails = data.data.data; 13 | setClick(`Total emails: ${emails.length}`); 14 | } catch (error) { 15 | setClick('Error Occured!') 16 | console.log(error); 17 | } 18 | }; 19 | 20 | return ( 21 | 27 | ); 28 | }; 29 | export default MyEmails; 30 | -------------------------------------------------------------------------------- /src/app/component/SnowfalBG.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import Snowfall from "react-snowfall"; 4 | 5 | const SnowfallBG = () => { 6 | return ( 7 | 15 | ); 16 | }; 17 | 18 | export default SnowfallBG; 19 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taqui-786/Devletter/dc30c504841627470f4b6de483f18deb09afcb73/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --foreground-rgb: 0, 0, 0; 7 | --background-start-rgb: 214, 219, 220; 8 | --background-end-rgb: 255, 255, 255; 9 | --border: 215 27.9% 16.9%; 10 | --input: 215 27.9% 16.9%; 11 | --ring: 263.4 70% 50.4%; 12 | } 13 | 14 | @media (prefers-color-scheme: dark) { 15 | :root { 16 | --foreground-rgb: 255, 255, 255; 17 | --background-start-rgb: 0, 0, 0; 18 | --background-end-rgb: 0, 0, 0; 19 | } 20 | } 21 | 22 | body { 23 | margin: 0; 24 | padding: 0; 25 | background: #030712 26 | } 27 | 28 | .member{ 29 | z-index: -1; 30 | transform: translateX(-50%) translateY(-10%) rotate(-6.48947deg) translateZ(0px); 31 | } -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Poppins } from "next/font/google"; 3 | import { Toaster } from 'sonner'; 4 | import "./globals.css"; 5 | import { siteConfig } from "./page"; 6 | 7 | const poppins = Poppins({ 8 | subsets: ["latin"], 9 | weight: ["400", "700"], 10 | variable: "--font-poppins", 11 | }); 12 | export const metadata: Metadata = { 13 | metadataBase: new URL("https://devletter.vercel.app"), 14 | title: { 15 | default: siteConfig.name, 16 | template: `%s - Newsletter for dev's`, 17 | }, 18 | description: siteConfig.description, 19 | 20 | // added new keywords for seo 21 | keywords: [ 22 | "devletter","newsletter", "dev","letter","Dev","Letter","@Taquiimam14","Taqui imam","Md","Taqui", "imam", "Md Taqui Imam", "programmer", "web developer","nextjs", "react" 23 | ], 24 | authors: [ 25 | { 26 | name: "Md Taqui Imam", 27 | url: "https://github.com/taqui-786", 28 | }, 29 | ], 30 | creator: "Md Taqui imam", 31 | 32 | openGraph: { 33 | type: "website", 34 | locale: "en_US", 35 | url: siteConfig.url, 36 | title: siteConfig.name, 37 | description: siteConfig.description, 38 | images: [`${siteConfig.url}/og-image.png`], 39 | siteName: siteConfig.name, 40 | }, 41 | twitter: { 42 | card: "summary_large_image", 43 | title: siteConfig.name, 44 | description: siteConfig.description, 45 | images: [`${siteConfig.url}/og-image.png`], 46 | creator: "@Taquiimam14", 47 | }, 48 | icons: { 49 | icon: "/favicon.ico", 50 | }, 51 | }; 52 | 53 | export default function RootLayout({ 54 | children, 55 | }: { 56 | children: React.ReactNode; 57 | }) { 58 | return ( 59 | 60 | 61 | 62 | {children} 63 | 64 | 65 | ); 66 | } 67 | 68 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import { ExternalLink, Github, Twitter } from "lucide-react"; 2 | import InputForm from "./component/InputForm"; 3 | import MemberJoined from "./component/MemberJoined"; 4 | import SnowfallBG from "./component/SnowfalBG"; 5 | import Link from "next/link"; 6 | 7 | 8 | export const siteConfig = { 9 | name: "Devletter", 10 | description: "Devletter a newsletter for dev's made with Nextjs", 11 | ogImage: "https://devletter.vercel.app/og-image.png", 12 | url: "https://devletter.vercel.app/", 13 | } 14 | export default function Home() { 15 | return ( 16 |
17 | 18 |
19 | 20 | 21 |
22 |

23 | Welcome To Dev's Newsletter 24 |

25 |

26 | Join us for weekly tech News and stay ahead with the latest tech trends, 27 | Checkout amazing projects, Get valuable blogs and tips, and be the 28 | first to know about upcoming hackathons. Don't miss out on the 29 | tech revolution ― Join us now and get our weekly dose of 30 | Devletter. 31 |

32 |
33 |
34 | 35 |
36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 |
48 |
49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /src/lib/EmailValidator.ts: -------------------------------------------------------------------------------- 1 | import { z} from 'zod' 2 | 3 | 4 | export const EmailSchema = z.object({ 5 | email: z.string().nonempty('Email is required').email("invalid E-mail") 6 | }) -------------------------------------------------------------------------------- /src/lib/Prisma.db.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from '@prisma/client' 2 | import "server-only" 3 | 4 | declare global { 5 | // eslint-disable-next-line no-var, no-unused-vars 6 | var cachedPrisma: PrismaClient 7 | } 8 | 9 | let prisma: PrismaClient 10 | if (process.env.NODE_ENV === 'production') { 11 | prisma = new PrismaClient() 12 | } else { 13 | if (!global.cachedPrisma) { 14 | global.cachedPrisma = new PrismaClient() 15 | } 16 | prisma = global.cachedPrisma 17 | } 18 | 19 | export const db = prisma -------------------------------------------------------------------------------- /src/lib/SendingEmail.ts: -------------------------------------------------------------------------------- 1 | 'use server' 2 | 3 | import {z} from 'zod' 4 | import { Resend } from 'resend' 5 | import { EmailSchema } from './EmailValidator' 6 | import { ThankyouEmail } from '@/app/component/EmailTemplates' 7 | import { renderAsync } from '@react-email/render' 8 | 9 | 10 | type input = z.infer 11 | 12 | export async function addEntry(data: input){ 13 | const result = EmailSchema.safeParse(data) 14 | if(result.success){ 15 | return {success: true, data: result.data } 16 | } 17 | if(result.error){ 18 | 19 | return {success: false, error: result.error.format() } 20 | } 21 | } 22 | 23 | 24 | type emailFormInput = z.infer 25 | 26 | const resend = new Resend(process.env.RESEND_API_KEY) 27 | export async function SendEmail (data: emailFormInput){ 28 | const emailTemplate = await renderAsync(ThankyouEmail({})) 29 | 30 | const result = EmailSchema.safeParse(data) 31 | if(result.success){ 32 | const {email} = result.data 33 | try { 34 | const data = await resend.emails.send({ 35 | from :"Devletter ", 36 | to: email as string, 37 | subject:"Taqui from Devletter", 38 | react: emailTemplate 39 | 40 | }) 41 | return {success:true, data} 42 | } catch (error) { 43 | return {success:false, error} 44 | 45 | } 46 | } 47 | if(result.error){ 48 | return {success: false, error: result.error.format() } 49 | } 50 | 51 | } 52 | 53 | 54 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'tailwindcss' 2 | 3 | const config: Config = { 4 | content: [ 5 | './src/pages/**/*.{js,ts,jsx,tsx,mdx}', 6 | './src/components/**/*.{js,ts,jsx,tsx,mdx}', 7 | './src/app/**/*.{js,ts,jsx,tsx,mdx}', 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 13 | 'gradient-conic': 14 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', 15 | }, 16 | }, 17 | }, 18 | plugins: [], 19 | } 20 | export default config 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | --------------------------------------------------------------------------------