Skip to content

Commit

Permalink
fix(root): 🚑 fixed email Invoice feature
Browse files Browse the repository at this point in the history
  • Loading branch information
sahrohit committed Aug 11, 2023
1 parent 3e85637 commit 4a0b352
Show file tree
Hide file tree
Showing 9 changed files with 287 additions and 153 deletions.
21 changes: 11 additions & 10 deletions apps/admin/src/generated/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,9 @@ export type Mutation = {
deleteCategory: Scalars["Boolean"];
deleteDiscount?: Maybe<Scalars["Boolean"]>;
deleteFromCart: Scalars["Boolean"];
emailInvoice: Scalars["Boolean"];
forgotPassword: Scalars["Boolean"];
generateInvoice?: Maybe<Scalars["String"]>;
login: UserResponse;
logout: Scalars["Boolean"];
register: UserResponse;
Expand Down Expand Up @@ -204,10 +206,19 @@ export type MutationDeleteFromCartArgs = {
quantity: Scalars["Int"];
};

export type MutationEmailInvoiceArgs = {
email: Scalars["String"];
orderId: Scalars["String"];
};

export type MutationForgotPasswordArgs = {
email: Scalars["String"];
};

export type MutationGenerateInvoiceArgs = {
orderId: Scalars["String"];
};

export type MutationLoginArgs = {
email: Scalars["String"];
password: Scalars["String"];
Expand Down Expand Up @@ -440,11 +451,9 @@ export type Query = {
allReviews?: Maybe<Array<ProductReview>>;
categories: Array<ProductCategory>;
categoriesSummary?: Maybe<Array<ProductCategoryWithProductCount>>;
emailInvoice: Scalars["Boolean"];
favourites: Array<Favourite>;
favouritesWithProduct: Array<Favourite>;
fetchCartItems?: Maybe<Array<Cart>>;
generate?: Maybe<Scalars["String"]>;
hello: Scalars["String"];
me?: Maybe<User>;
orderById?: Maybe<OrderDetail>;
Expand All @@ -466,14 +475,6 @@ export type QueryAllReviewsArgs = {
productId: Scalars["Int"];
};

export type QueryEmailInvoiceArgs = {
orderId: Scalars["String"];
};

export type QueryGenerateArgs = {
orderId: Scalars["String"];
};

export type QueryOrderByIdArgs = {
orderId: Scalars["String"];
};
Expand Down
37 changes: 8 additions & 29 deletions apps/api/src/resolvers/invoice.ts
Original file line number Diff line number Diff line change
@@ -1,62 +1,41 @@
import { COMPANY } from "../constants";
import { OrderDetail } from "../entities/OrderDetail";
import { Arg, Ctx, Query, Resolver, UseMiddleware } from "type-graphql";
import { Arg, Mutation, Resolver, UseMiddleware } from "type-graphql";
import easyinvoice from "easyinvoice";
import fs from "fs";
import { sendEmailWithAttachment } from "../utils/sendEmail";
import { type MyContext } from "../types";
import { User } from "../entities/User";
import { invoiceTemplate } from "../static/invoiceTemplate";
import { isAuth } from "../middlewares/isAuth";

@Resolver()
export class InvoiceResolver {
@Query(() => String, { nullable: true })
@Mutation(() => String, { nullable: true })
@UseMiddleware(isAuth)
async generate(
async generateInvoice(
@Arg("orderId", () => String) orderId: string
): Promise<string> {
return (await easyinvoice.createInvoice(await getSampleData(orderId))).pdf;
}

@Query(() => Boolean)
@Mutation(() => Boolean)
@UseMiddleware(isAuth)
async emailInvoice(
@Arg("orderId", () => String) orderId: string,
@Ctx() { req }: MyContext
@Arg("email", () => String) email: string
): Promise<boolean> {
const user = await User.findOne({ where: { id: req.session?.userId } });

if (!user) {
return false;
}

await easyinvoice.createInvoice(
await getSampleData(orderId),
async function (result) {
// TODO: Email doesn't attach the file yet.
// fs.writeFile(`${orderId}.pdf`, result.pdf, "base64", function (err) {
// if (err) {
// console.error(err);
// return;
// }
// });
await sendEmailWithAttachment(
user?.email,
email,
`Invoice for ${orderId}`,
invoiceTemplate(orderId),
[
{
path: `${orderId}.pdf`,
filename: `INVOICE-${orderId}.pdf`,
path: `data:application/pdf;base64,${result.pdf}`,
},
]
);
// fs.unlink(`${orderId}.pdf`, (err) => {
// if (err) {
// console.error(err);
// return;
// }
// });
}
);
return true;
Expand Down
30 changes: 12 additions & 18 deletions apps/api/src/utils/sendEmail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@ import { COMPANY, __prod__ } from "../constants";
import { Attachment } from "nodemailer/lib/mailer";
import SMTPTransport from "nodemailer/lib/smtp-transport";

const transportOptions: SMTPTransport.Options = {
host: process.env.RESEND_HOST,
port: parseInt(process.env.RESEND_PORT!),
secure: __prod__,
auth: {
user: process.env.RESEND_AUTH_USER,
pass: process.env.RESEND_AUTH_PASS,
},
};

export async function sendEmail(
to: string,
subject: string,
html: string
): Promise<SMTPTransport.SentMessageInfo> {
const transporter = nodemailer.createTransport({
host: "smtp.resend.com",
port: 465,
secure: true,
auth: {
user: "resend",
pass: process.env.RESEND_API_KEY,
},
});
const transporter = nodemailer.createTransport(transportOptions);

const info = await transporter.sendMail({
from: `"${COMPANY.name} 👻" <[email protected]>`,
Expand All @@ -37,15 +39,7 @@ export async function sendEmailWithAttachment(
html: string,
attachments: Attachment[]
): Promise<SMTPTransport.SentMessageInfo> {
const transporter = nodemailer.createTransport({
host: "smtp.resend.com",
port: 465,
secure: __prod__,
auth: {
user: "resend",
pass: process.env.RESEND_API_KEY,
},
});
const transporter = nodemailer.createTransport(transportOptions);

const info = await transporter.sendMail({
from: `"${COMPANY.name} 👻" <[email protected]>`,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { Button, Card, HStack, Stack, useToast } from "@chakra-ui/react";
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import * as Yup from "yup";
import { BiMailSend } from "react-icons/bi";
import { useEmailInvoiceMutation } from "@/generated/graphql";
import InputField from "@/components/ui/InputField";

interface FormValues {
email: string;
}

const EmailInvoiceFormSchema = Yup.object({
email: Yup.string().email().required("Required"),
});

const EmailInvoice = ({ orderId }: { orderId: string }) => {
const toast = useToast();

const {
register,
handleSubmit,
formState: { errors, touchedFields, isSubmitting },
} = useForm<FormValues>({
defaultValues: {
email: "",
},
resolver: yupResolver(EmailInvoiceFormSchema),
});

const [emailInvoiceMutation, { error }] = useEmailInvoiceMutation();

return (
<Card w="full" py="8" px={{ base: "4", md: "10" }} shadow="none">
<form
onSubmit={handleSubmit(async (values) => {
const res = await emailInvoiceMutation({
variables: {
email: values.email,
orderId,
},
});

if (!res.data?.emailInvoice) {
toast({
title: "An Error Occured",
description: error?.message ?? "Please try again later.",
status: "error",
duration: 4000,
isClosable: true,
});
} else {
toast({
title: "Email sent",
description: "We've sent you an email with an invoice.",
status: "success",
duration: 4000,
isClosable: true,
});
}
})}
>
<Stack spacing="6">
<InputField
register={{ ...register("email") }}
error={errors.email}
touched={touchedFields.email}
name="email"
type="email"
size="lg"
autoComplete="email"
label="Email"
placeholder="[email protected]"
/>
<HStack w="full" justify="center">
<Button
type="submit"
fontSize="md"
colorScheme="blue"
leftIcon={<BiMailSend fontSize={20} />}
isLoading={isSubmitting}
>
Send Invoice
</Button>
</HStack>
</Stack>
</form>
</Card>
);
};

export default EmailInvoice;
49 changes: 31 additions & 18 deletions apps/storefront/src/components/pages/account/order/OrderCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { Link } from "@chakra-ui/next-js";
import {
OrderDetail,
OrderItem,
useGenerateInvoiceLazyQuery,
useGenerateInvoiceMutation,
} from "@/generated/graphql";
import {
OrderInfo,
Expand All @@ -35,8 +35,11 @@ import {
import { PriceTag } from "@/components/shared/product/PriceTag";
import { capitalize } from "@/utils/helpers";
import ConfirmationModal from "@/components/helpers/ConfirmationModal";
import ModalButton from "@/components/ui/ModalButton";
import DividerWithText from "@/components/ui/DividerWithText";
import { KHALTI_LOGO } from "../../cart/checkout/PaymentSelector";
import { CreateReviewButton } from "../../product/review/ProductReview";
import EmailInvoice from "./EmailInvoice";

interface OrderCardProps {
orderItem: OrderDetail;
Expand All @@ -48,8 +51,8 @@ const OrderCard = ({ orderItem }: OrderCardProps) => {
(payment) => payment.status === "COMPLETED"
);

const [generateInvoice, { loading: invoiceLoading }] =
useGenerateInvoiceLazyQuery();
const [generateInvoiceMutation, { loading: invoiceLoading }] =
useGenerateInvoiceMutation();

const subTotal = useMemo(
() =>
Expand Down Expand Up @@ -86,24 +89,34 @@ const OrderCard = ({ orderItem }: OrderCardProps) => {
<Button size="sm" as={Link} href={`/order/${orderItem.id}`}>
View Order
</Button>
<Button
<ModalButton
size="sm"
leftIcon={<BiDownload />}
isLoading={invoiceLoading}
onClick={async () => {
const res = await generateInvoice({
variables: {
orderId: orderItem.id,
},
});
window.open(
`data:application/pdf;base64,${res.data?.generate}`,
"_blank"
);
}}
buttonText="View Invoice"
modalHeader="Invoice"
>
Download Invoice
</Button>
<VStack w="full">
<Button
leftIcon={<BiDownload />}
isLoading={invoiceLoading}
onClick={async () => {
const res = await generateInvoiceMutation({
variables: {
orderId: orderItem.id,
},
});
const downloadLink = document.createElement("a");
downloadLink.href = `data:application/pdf;base64,${res.data?.generateInvoice}`;
downloadLink.download = `invoice-${orderItem.id}.pdf`;
downloadLink.click();
}}
>
Download Invoice
</Button>
<DividerWithText w="full">OR</DividerWithText>
<EmailInvoice orderId={orderItem.id} />
</VStack>
</ModalButton>
</HStack>
</CardHeader>
<Divider />
Expand Down
Loading

2 comments on commit 4a0b352

@vercel
Copy link

@vercel vercel bot commented on 4a0b352 Aug 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 4a0b352 Aug 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

ecommerce-admin-client – ./apps/admin

ecommerce-admin-client-sahrohit.vercel.app
ecommerce-admin-client-git-main-sahrohit.vercel.app
ecommerce-admin-client.vercel.app

Please sign in to comment.