Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Send email integration added using resend #301

Merged
merged 3 commits into from
Jun 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ NEXT_PUBLIC_GEMINI_API_KEY_FLOWCHART=your-gemini-api-key
NEXT_PUBLIC_EMAILJS_TEMPLATE_ID=your_id
NEXT_PUBLIC_EMAILJS_API_KEY=your_id
NEXT_PUBLIC_EMAILJS_SERVICE_ID=your_id
RESEND_API_KEY=your_api_key
#use these variables only ;)
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@
"convex": "^1.11.3",
"cors": "^2.8.5",
"editorjs-button": "^3.0.3",
"emailjs-com": "^3.2.0",
"emailjs": "^4.0.3",
"emailjs-com": "^3.2.0",
"embla-carousel-autoplay": "^8.0.4",
"embla-carousel-react": "^8.0.4",
"express": "^4.19.2",
Expand All @@ -66,6 +66,7 @@
"react-spring": "^9.7.3",
"remark": "^15.0.1",
"remark-parse": "^11.0.0",
"resend": "^3.3.0",
"sharp": "^0.33.3",
"sonner": "^1.4.41",
"tailwind-merge": "^2.3.0",
Expand Down
23 changes: 23 additions & 0 deletions src/app/api/mail/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// app/api/send-email.ts
import { NextRequest, NextResponse } from 'next/server';
import { Resend } from 'resend';

const resend = new Resend(process.env.RESEND_API_KEY || '');

export async function POST(req: NextRequest) {
const { to, text } = await req.json();
const subject = 'Welcome to Resend!';
try {
const response = await resend.emails.send({
from: 'Acme <onboarding@resend.dev>',
to,
subject,
text,
});

return NextResponse.json({ success: true, data: response });
} catch (error: any) {
console.error('Error sending email:', error);
return NextResponse.json({ success: false, error: error.message }, { status: 500 });
}
}
5 changes: 5 additions & 0 deletions src/app/contributors/ContributorsData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,9 @@ export const ContributorsData = [
github: "https://github.com/Ayushmaanagarwal1211",
imageUrl: "https://avatars.githubusercontent.com/u/118350936?v=4",
},
{
name: "Ramakrushna Biswal",
github: "https://github.com/RamakrushnaBiswal",
imageUrl: "https://avatars.githubusercontent.com/u/125277258?v=4",
},
];
4 changes: 4 additions & 0 deletions src/components/shared/InviteModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { Separator } from "../ui/separator";
import Image from "next/image";
import Link from "next/link";
import { toast } from "sonner";
import EmailForm from "../ui/EmailForm";

export default function InviteModal() {
const teamName = useSelector((state: RootState) => state.team.teamName);
Expand Down Expand Up @@ -125,6 +126,9 @@ export default function InviteModal() {
/>
</Link>
</div>
<div>
<EmailForm url={URL}/>
</div>
</div>
</DialogDescription>
</DialogContent>
Expand Down
162 changes: 162 additions & 0 deletions src/components/ui/EmailForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
"use client";

import { useState, ChangeEvent, FormEvent } from "react";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/components/ui/card";
import { toast } from "sonner";

interface EmailFormProps {
url: string;
}

export default function EmailForm({ url }: EmailFormProps) {
const [emails, setEmails] = useState<string[]>([]);
const [inputValue, setInputValue] = useState("");
const [status, setStatus] = useState<string>("");

const handleEmailInput = (e: ChangeEvent<HTMLInputElement>) => {
setInputValue(e.target.value);
};

const handleAddEmail = () => {
const newEmails = inputValue.split(",").map((email) => email.trim());
const validEmails = newEmails.filter(isValidEmail);
const invalidEmails = newEmails.filter((email) => !isValidEmail(email));

setEmails([...emails, ...validEmails]);
setInputValue("");

invalidEmails.forEach((email) => {
toast.error(`Invalid email: ${email}`);
});
};

const handleRemoveEmail = (email: string) => {
setEmails(emails.filter((e) => e !== email));
};

const isValidEmail = (email: string) => {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
};

const handleSendEmails = async () => {
setStatus("Sending...");

try {
const response = await fetch("/api/mail", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
to: emails,
text: `Here is the link for you: ${url}`,
}),
});

const result = await response.json();

if (result.success) {
setStatus("Emails sent successfully!");
console.log("Response from API:", result);
} else {
setStatus(`Failed to send emails: ${result.error}`);
}
} catch (error: any) {
setStatus(`Error: ${error.message}`);
}
handleRemoveEmail(emails[0]);
setInputValue("");
setStatus("");
};

return (
<div className="w-full max-w-md">
<CardHeader>
<CardTitle className="text-md">Add Email Addresses</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="flex flex-col gap-2">
<Input
type="text"
placeholder="Enter email address"
value={inputValue}
onChange={handleEmailInput}
className="px-3 py-1.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 max-h-32"
/>
<div className="flex flex-wrap items-center gap-2 overflow-y-scroll max-h-24">
{emails.map((email) => (
<div
key={email}
className={`flex items-center gap-2 rounded-full p-0.5 fs-sm font-medium ${
isValidEmail(email)
? "bg-gray-100 dark:bg-gray-800 text-green-500"
: "bg-red-100 dark:bg-red-900 text-red-500"
}`}
>
<span>{isValidEmail(email) ? <CheckIcon /> : <XIcon />}</span>
<span>{email}</span>
<button
type="button"
className="text-gray-400 hover:text-gray-500 dark:hover:text-gray-300"
onClick={() => handleRemoveEmail(email)}
>
<XIcon />
</button>
</div>
))}
</div>
</div>
<div className="flex justify-end">
<Button size="sm" onClick={handleAddEmail}>
Add Email
</Button>
<Button size="sm" onClick={handleSendEmails} className="ml-2" variant="secondary">
Send Emails
</Button>
</div>
</div>
</CardContent>
{status && <p>{status}</p>}
</div>
);
}

function CheckIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="20 6 9 17 4 12"></polyline>
</svg>
);
}

function XIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
);
}
Loading