email working for order form
@ -10,13 +10,17 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-slot": "^1.1.0",
|
"@radix-ui/react-slot": "^1.1.0",
|
||||||
|
"@react-email/components": "^0.0.22",
|
||||||
|
"@react-email/tailwind": "^0.0.19",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
"lucide-react": "^0.427.0",
|
"lucide-react": "^0.427.0",
|
||||||
"next": "14.1.4",
|
"next": "14.1.4",
|
||||||
"nodemailer": "^6.9.14",
|
"nodemailer": "^6.9.14",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
"resend": "^3.5.0",
|
||||||
"sharp": "^0.33.4",
|
"sharp": "^0.33.4",
|
||||||
"tailwind-merge": "^2.5.2",
|
"tailwind-merge": "^2.5.2",
|
||||||
"tailwindcss-animate": "^1.0.7"
|
"tailwindcss-animate": "^1.0.7"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 5.6 MiB |
|
Before Width: | Height: | Size: 867 KiB |
|
Before Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 619 KiB |
|
Before Width: | Height: | Size: 732 KiB |
|
Before Width: | Height: | Size: 505 KiB |
@ -2,7 +2,7 @@
|
|||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"name": "design update",
|
"name": "design update",
|
||||||
"photo": "batch1/design update.png",
|
"photo": "batch1/t shirt mockup 3.jpg",
|
||||||
"price": 25,
|
"price": 25,
|
||||||
"size": "Youth-S/M/L/2XL | Adult-S/M/L/2XL",
|
"size": "Youth-S/M/L/2XL | Adult-S/M/L/2XL",
|
||||||
"inStock": "yes",
|
"inStock": "yes",
|
||||||
@ -42,7 +42,7 @@
|
|||||||
{
|
{
|
||||||
"id": 5,
|
"id": 5,
|
||||||
"name": "design 6 update-01",
|
"name": "design 6 update-01",
|
||||||
"photo": "batch1/design 6 update-01.png",
|
"photo": "batch1/t shirt mockup 6.jpg",
|
||||||
"price": 25,
|
"price": 25,
|
||||||
"size": "Youth-S/M/L/2XL | Adult-S/M/L/2XL",
|
"size": "Youth-S/M/L/2XL | Adult-S/M/L/2XL",
|
||||||
"inStock": "yes",
|
"inStock": "yes",
|
||||||
@ -72,7 +72,7 @@
|
|||||||
{
|
{
|
||||||
"id": 9,
|
"id": 9,
|
||||||
"name": "design 4-01",
|
"name": "design 4-01",
|
||||||
"photo": "batch1/design 4-01.png",
|
"photo": "batch1/t shirt mockup 4.jpg",
|
||||||
"price": 25,
|
"price": 25,
|
||||||
"size": "Youth-S/M/L/2XL | Adult-S/M/L/2XL",
|
"size": "Youth-S/M/L/2XL | Adult-S/M/L/2XL",
|
||||||
"inStock": "yes",
|
"inStock": "yes",
|
||||||
@ -99,16 +99,6 @@
|
|||||||
"favorite": "no",
|
"favorite": "no",
|
||||||
"description": "It's not an option.."
|
"description": "It's not an option.."
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": 12,
|
|
||||||
"name": "75-01",
|
|
||||||
"photo": "batch1/75-01.png",
|
|
||||||
"price": 25,
|
|
||||||
"size": "Youth-S/M/L/2XL | Adult-S/M/L/2XL",
|
|
||||||
"inStock": "yes",
|
|
||||||
"favorite": "no",
|
|
||||||
"description": "It's not an option.."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": 13,
|
"id": 13,
|
||||||
"name": "Mockup 119",
|
"name": "Mockup 119",
|
||||||
@ -132,7 +122,7 @@
|
|||||||
{
|
{
|
||||||
"id": 15,
|
"id": 15,
|
||||||
"name": "10-01",
|
"name": "10-01",
|
||||||
"photo": "batch1/10-01.png",
|
"photo": "batch1/t shirt mockup 10.jpg",
|
||||||
"price": 25,
|
"price": 25,
|
||||||
"size": "Youth-S/M/L/2XL | Adult-S/M/L/2XL",
|
"size": "Youth-S/M/L/2XL | Adult-S/M/L/2XL",
|
||||||
"inStock": "yes",
|
"inStock": "yes",
|
||||||
@ -142,7 +132,7 @@
|
|||||||
{
|
{
|
||||||
"id": 16,
|
"id": 16,
|
||||||
"name": "design 7 update-01",
|
"name": "design 7 update-01",
|
||||||
"photo": "batch1/design 7 update-01.png",
|
"photo": "batch1/t shirt mockup 7.jpg",
|
||||||
"price": 25,
|
"price": 25,
|
||||||
"size": "Youth-S/M/L/2XL | Adult-S/M/L/2XL",
|
"size": "Youth-S/M/L/2XL | Adult-S/M/L/2XL",
|
||||||
"inStock": "yes",
|
"inStock": "yes",
|
||||||
@ -182,7 +172,7 @@
|
|||||||
{
|
{
|
||||||
"id": 20,
|
"id": 20,
|
||||||
"name": "design 8 update-01",
|
"name": "design 8 update-01",
|
||||||
"photo": "batch1/design 8 update-01.png",
|
"photo": "batch1/t shirt mockup 8.jpg",
|
||||||
"price": 25,
|
"price": 25,
|
||||||
"size": "Youth-S/M/L/2XL | Adult-S/M/L/2XL",
|
"size": "Youth-S/M/L/2XL | Adult-S/M/L/2XL",
|
||||||
"inStock": "yes",
|
"inStock": "yes",
|
||||||
|
|||||||
BIN
frontend/public/batch1/t shirt mockup 10.jpg
Normal file
|
After Width: | Height: | Size: 573 KiB |
BIN
frontend/public/batch1/t shirt mockup 3.jpg
Normal file
|
After Width: | Height: | Size: 562 KiB |
BIN
frontend/public/batch1/t shirt mockup 4.jpg
Normal file
|
After Width: | Height: | Size: 573 KiB |
BIN
frontend/public/batch1/t shirt mockup 6.jpg
Normal file
|
After Width: | Height: | Size: 597 KiB |
BIN
frontend/public/batch1/t shirt mockup 7.jpg
Normal file
|
After Width: | Height: | Size: 552 KiB |
BIN
frontend/public/batch1/t shirt mockup 8.jpg
Normal file
|
After Width: | Height: | Size: 588 KiB |
44
frontend/src/app/actions/sendEmail.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
"use server"
|
||||||
|
|
||||||
|
import { Resend } from "resend"
|
||||||
|
import { validateString } from "@/lib/utils";
|
||||||
|
import OrderFormEmail from "../emailTemplates/order-form-email";
|
||||||
|
import React from "react";
|
||||||
|
import 'dotenv/config';
|
||||||
|
|
||||||
|
|
||||||
|
const resend = new Resend(process.env.RESEND_API_KEY);
|
||||||
|
|
||||||
|
|
||||||
|
export const sendEmail = async (formData: FormData) => {
|
||||||
|
//rate limit
|
||||||
|
|
||||||
|
//only allow authenticated user as well
|
||||||
|
|
||||||
|
const senderEmail = formData.get('senderEmail');
|
||||||
|
const message = formData.get('message');
|
||||||
|
|
||||||
|
if (!validateString(senderEmail, 500)){
|
||||||
|
return{ error: "Invalid sender Email",};
|
||||||
|
}
|
||||||
|
if (!validateString(message, 5000)){
|
||||||
|
return{ error: "Invalid message",};
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log("Running on sever");
|
||||||
|
// console.log(formData.get("senderEmail"));
|
||||||
|
// console.log(formData.get("message"));
|
||||||
|
|
||||||
|
try {
|
||||||
|
await resend.emails.send({
|
||||||
|
from:"Order Form <orders@kevosattire.com>",
|
||||||
|
to:"arcjcb11@gmail.com",
|
||||||
|
subject: "New Order",
|
||||||
|
reply_to: senderEmail as string,
|
||||||
|
// text: message as string,
|
||||||
|
react: React.createElement(OrderFormEmail, {message: message as string, senderEmail: senderEmail as string}),
|
||||||
|
});
|
||||||
|
} catch (error: unknown){
|
||||||
|
return console.log(error)
|
||||||
|
}
|
||||||
|
};
|
||||||
39
frontend/src/app/emailTemplates/order-form-email.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import React from "react";
|
||||||
|
import {
|
||||||
|
Html,
|
||||||
|
Body,
|
||||||
|
Head,
|
||||||
|
Heading,
|
||||||
|
Hr,
|
||||||
|
Container,
|
||||||
|
Preview,
|
||||||
|
Section,
|
||||||
|
Text,
|
||||||
|
} from "@react-email/components";
|
||||||
|
import { Tailwind } from "@react-email/tailwind";
|
||||||
|
|
||||||
|
type OrderFormEmailProps = {
|
||||||
|
message: string;
|
||||||
|
senderEmail: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function OrderFormEmail({ message, senderEmail }: OrderFormEmailProps) {
|
||||||
|
return (
|
||||||
|
<Html>
|
||||||
|
<Head />
|
||||||
|
<Preview>New order from kevo's attire.</Preview>
|
||||||
|
<Tailwind>
|
||||||
|
<Body className="bg-slate-700">
|
||||||
|
<Container>
|
||||||
|
<Section className="bg-white border-black my-10 px-10 py-4 rounded-md">
|
||||||
|
<Heading className="leading-tight">You received a new order from the order form.</Heading>
|
||||||
|
<Text>{message}</Text>
|
||||||
|
<Hr />
|
||||||
|
<Text>The sender's email is: {senderEmail}</Text>
|
||||||
|
</Section>
|
||||||
|
</Container>
|
||||||
|
</Body>
|
||||||
|
</Tailwind>
|
||||||
|
</Html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -8,6 +8,7 @@ import Modal from "../components/Modal/Modal";
|
|||||||
import ClientPortal from "@/components/ClientPortal/ClientPortal";
|
import ClientPortal from "@/components/ClientPortal/ClientPortal";
|
||||||
import styles from "@/styles/Home.module.css";
|
import styles from "@/styles/Home.module.css";
|
||||||
import { Poppins } from "next/font/google";
|
import { Poppins } from "next/font/google";
|
||||||
|
import { sendEmail } from "./actions/sendEmail";
|
||||||
|
|
||||||
const Popp = Poppins({ weight: "400", subsets: ["latin"] });
|
const Popp = Poppins({ weight: "400", subsets: ["latin"] });
|
||||||
|
|
||||||
@ -40,6 +41,16 @@ export default function Home() {
|
|||||||
</Modal>
|
</Modal>
|
||||||
</div> */}
|
</div> */}
|
||||||
|
|
||||||
|
<form className="mt-10 flex flex-col w-full items-center gap-4" action={async (formData) => {await sendEmail(formData)}}>
|
||||||
|
<h2 className="text-orange-600 text-2xl">Order Form</h2>
|
||||||
|
<input className="h-14 px-4 rounded-lg border-black bg-slate-600 lg:w-1/3 md:w-1/2 text-[#EEEEEE]" type="email" required maxLength={500} placeholder="Your email" name="senderEmail"/>
|
||||||
|
|
||||||
|
<textarea className="h-52 my-3 rounded-lg lg:w-1/3 md:w-1/2 bg-slate-600 p-4 text-[#EEEEEE]" name="message" placeholder="Your order" required maxLength={500}/>
|
||||||
|
|
||||||
|
<button className="w-[8rem] flex items-center justify-center gap-2 h-[3rem] bg-slate-600 rounded-full outline-none transition-all text-white hover:bg-slate-500 hover:scale-110 hover:focus:scale-110 active:scale-105" type="submit" value="Submit">Submit</button>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<div className="flex w-full h-[250px] items-center text-gray-400">
|
<div className="flex w-full h-[250px] items-center text-gray-400">
|
||||||
<div className="font-bold ">
|
<div className="font-bold ">
|
||||||
@ -47,7 +58,7 @@ export default function Home() {
|
|||||||
<a href="mailto:kevosattire@gmail.com" className="text-emerald-500">
|
<a href="mailto:kevosattire@gmail.com" className="text-emerald-500">
|
||||||
kevosattire@gmail.com
|
kevosattire@gmail.com
|
||||||
</a>
|
</a>
|
||||||
to order!
|
for any questions!
|
||||||
</div>
|
</div>
|
||||||
<div></div>
|
<div></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -4,3 +4,10 @@ import { twMerge } from "tailwind-merge"
|
|||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs))
|
return twMerge(clsx(inputs))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const validateString = (value: unknown, maxLength: number) => {
|
||||||
|
if(!value || typeof value !== "string" || value.length > maxLength){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||