Skip to content

Commit

Permalink
feat: add email sending (#32)
Browse files Browse the repository at this point in the history
* feat: add basic email sending functionality

* feat: add toast after submit form
  • Loading branch information
Skolaczk authored Feb 18, 2024
1 parent 025a952 commit 0b18dd2
Show file tree
Hide file tree
Showing 8 changed files with 456 additions and 63 deletions.
422 changes: 370 additions & 52 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
"react-dom": "^18",
"react-hook-form": "^7.50.0",
"react-vertical-timeline-component": "^3.6.0",
"resend": "^3.2.0",
"sonner": "^1.4.0",
"tailwind-merge": "^2.2.1",
"zod": "^3.22.4"
},
Expand Down
28 changes: 28 additions & 0 deletions src/actions/send-email.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use server';

import { Resend } from 'resend';

import { TFormSchema } from '@/components/contact';
import { env } from '@/env.mjs';

const resend = new Resend(env.RESEND_API_KEY);

export const sendEmail = async ({ email, message }: TFormSchema) => {
try {
await resend.emails.send({
from: 'Contact Form <[email protected]>',
to: '[email protected]',
subject: 'Message from contact form',
reply_to: email,
text: `email: ${email} \nmessage: ${message}`,
});

return {
data: 'Email sent successfully!',
};
} catch (error) {
return {
error: 'Something went wrong!',
};
}
};
6 changes: 5 additions & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { PropsWithChildren } from 'react';
import type { Metadata } from 'next';

import { ThemeProvider } from '@/components/theme-provider';
import { Toaster } from '@/components/toaster';
import { fonts } from '@/lib/fonts';
import { siteConfig } from '@/lib/site-config';
import { cn } from '@/lib/utils';
Expand Down Expand Up @@ -46,7 +47,10 @@ const RootLayout = ({ children }: PropsWithChildren) => {
return (
<html lang="en" suppressHydrationWarning>
<body className={cn('min-h-screen font-sans', fonts)}>
<ThemeProvider attribute="class">{children}</ThemeProvider>
<ThemeProvider attribute="class">
{children}
<Toaster position="bottom-left" />
</ThemeProvider>
</body>
</html>
);
Expand Down
14 changes: 11 additions & 3 deletions src/components/contact.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { toast } from 'sonner';
import { z } from 'zod';

import { sendEmail } from '@/actions/send-email';
import { Button } from '@/components/button';
import { Icons } from '@/components/icons';
import { SectionHeading } from '@/components/section-heading';
Expand All @@ -17,7 +19,7 @@ const formSchema = z.object({
message: z.string().min(1, { message: 'Message is required' }),
});

type TFormSchema = z.infer<typeof formSchema>;
export type TFormSchema = z.infer<typeof formSchema>;

export const Contact = () => {
const {
Expand All @@ -27,9 +29,15 @@ export const Contact = () => {
formState: { errors },
} = useForm<TFormSchema>({ resolver: zodResolver(formSchema) });

const onSubmit = (values: TFormSchema) => {
console.log(values);
const onSubmit = async (values: TFormSchema) => {
const { data, error } = await sendEmail(values);

if (error) {
toast.error(error);
return;
}

toast.success(data);
reset();
};

Expand Down
32 changes: 32 additions & 0 deletions src/components/toaster.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use client';

import { useTheme } from 'next-themes';
import { Toaster as Sonner } from 'sonner';

type ToasterProps = React.ComponentProps<typeof Sonner>;

const Toaster = ({ ...props }: ToasterProps) => {
const { theme = 'system' } = useTheme();

return (
<Sonner
theme={theme as ToasterProps['theme']}
className="toaster group"
toastOptions={{
classNames: {
toast:
'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-none',
description:
'group-[.toast]:text-muted-foreground group-[.toast]:text-sm group-[.toast]:font-normal',
actionButton:
'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',
cancelButton:
'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground',
},
}}
{...props}
/>
);
};

export { Toaster };
11 changes: 6 additions & 5 deletions src/env.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import { z } from 'zod';

export const env = createEnv({
server: {
NEXT_PUBLIC_SITE_URL: z.string().url().optional(),
NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION_ID: z.string().min(1).optional(),
SITE_URL: z.string().url().optional(),
GOOGLE_SITE_VERIFICATION_ID: z.string().min(1).optional(),
RESEND_API_KEY: z.string().min(1).optional(),
},
runtimeEnv: {
NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL,
NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION_ID:
process.env.NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION_ID,
SITE_URL: process.env.SITE_URL,
GOOGLE_SITE_VERIFICATION_ID: process.env.GOOGLE_SITE_VERIFICATION_ID,
RESEND_API_KEY: process.env.RESEND_API_KEY,
},
});
4 changes: 2 additions & 2 deletions src/lib/site-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ export const siteConfig = {
'Next-auth',
'Prisma',
],
url: env.NEXT_PUBLIC_SITE_URL || 'https://example.com',
googleSiteVerificationId: env.NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION_ID || '',
url: env.SITE_URL || 'https://example.com',
googleSiteVerificationId: env.GOOGLE_SITE_VERIFICATION_ID || '',
};

0 comments on commit 0b18dd2

Please sign in to comment.