email working for order form
@ -10,13 +10,17 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@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",
|
||||
"clsx": "^2.1.1",
|
||||
"dotenv": "^16.4.5",
|
||||
"lucide-react": "^0.427.0",
|
||||
"next": "14.1.4",
|
||||
"nodemailer": "^6.9.14",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"resend": "^3.5.0",
|
||||
"sharp": "^0.33.4",
|
||||
"tailwind-merge": "^2.5.2",
|
||||
"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,
|
||||
"name": "design update",
|
||||
"photo": "batch1/design update.png",
|
||||
"photo": "batch1/t shirt mockup 3.jpg",
|
||||
"price": 25,
|
||||
"size": "Youth-S/M/L/2XL | Adult-S/M/L/2XL",
|
||||
"inStock": "yes",
|
||||
@ -42,7 +42,7 @@
|
||||
{
|
||||
"id": 5,
|
||||
"name": "design 6 update-01",
|
||||
"photo": "batch1/design 6 update-01.png",
|
||||
"photo": "batch1/t shirt mockup 6.jpg",
|
||||
"price": 25,
|
||||
"size": "Youth-S/M/L/2XL | Adult-S/M/L/2XL",
|
||||
"inStock": "yes",
|
||||
@ -72,7 +72,7 @@
|
||||
{
|
||||
"id": 9,
|
||||
"name": "design 4-01",
|
||||
"photo": "batch1/design 4-01.png",
|
||||
"photo": "batch1/t shirt mockup 4.jpg",
|
||||
"price": 25,
|
||||
"size": "Youth-S/M/L/2XL | Adult-S/M/L/2XL",
|
||||
"inStock": "yes",
|
||||
@ -99,16 +99,6 @@
|
||||
"favorite": "no",
|
||||
"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,
|
||||
"name": "Mockup 119",
|
||||
@ -132,7 +122,7 @@
|
||||
{
|
||||
"id": 15,
|
||||
"name": "10-01",
|
||||
"photo": "batch1/10-01.png",
|
||||
"photo": "batch1/t shirt mockup 10.jpg",
|
||||
"price": 25,
|
||||
"size": "Youth-S/M/L/2XL | Adult-S/M/L/2XL",
|
||||
"inStock": "yes",
|
||||
@ -142,7 +132,7 @@
|
||||
{
|
||||
"id": 16,
|
||||
"name": "design 7 update-01",
|
||||
"photo": "batch1/design 7 update-01.png",
|
||||
"photo": "batch1/t shirt mockup 7.jpg",
|
||||
"price": 25,
|
||||
"size": "Youth-S/M/L/2XL | Adult-S/M/L/2XL",
|
||||
"inStock": "yes",
|
||||
@ -182,7 +172,7 @@
|
||||
{
|
||||
"id": 20,
|
||||
"name": "design 8 update-01",
|
||||
"photo": "batch1/design 8 update-01.png",
|
||||
"photo": "batch1/t shirt mockup 8.jpg",
|
||||
"price": 25,
|
||||
"size": "Youth-S/M/L/2XL | Adult-S/M/L/2XL",
|
||||
"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 styles from "@/styles/Home.module.css";
|
||||
import { Poppins } from "next/font/google";
|
||||
import { sendEmail } from "./actions/sendEmail";
|
||||
|
||||
const Popp = Poppins({ weight: "400", subsets: ["latin"] });
|
||||
|
||||
@ -40,6 +41,16 @@ export default function Home() {
|
||||
</Modal>
|
||||
</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 */}
|
||||
<div className="flex w-full h-[250px] items-center text-gray-400">
|
||||
<div className="font-bold ">
|
||||
@ -47,7 +58,7 @@ export default function Home() {
|
||||
<a href="mailto:kevosattire@gmail.com" className="text-emerald-500">
|
||||
kevosattire@gmail.com
|
||||
</a>
|
||||
to order!
|
||||
for any questions!
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
|
||||
@ -4,3 +4,10 @@ import { twMerge } from "tailwind-merge"
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
||||
export const validateString = (value: unknown, maxLength: number) => {
|
||||
if(!value || typeof value !== "string" || value.length > maxLength){
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||