├── .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 |
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 |
84 | {tips?.map((tip) => (
85 |
86 | {tip.description}
87 |
88 | ))}
89 |
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 |
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 |
25 | {click}
26 |
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 |
--------------------------------------------------------------------------------