Rumah > hujung hadapan web > tutorial js > Cara Membuat Borang Hubungan E-mel Dinamik dalam Next.js Menggunakan Hantar Semula dan Zod

Cara Membuat Borang Hubungan E-mel Dinamik dalam Next.js Menggunakan Hantar Semula dan Zod

PHPz
Lepaskan: 2024-08-10 18:43:32
asal
364 orang telah melayarinya

pengenalan

Next.js ialah rangka kerja susunan penuh yang berkuasa yang membolehkan kami membina aplikasi dengan ciri bahagian hadapan dan bahagian belakang. Ia sangat fleksibel dan boleh digunakan untuk segala-galanya daripada tapak web statik mudah kepada apl web yang kompleks. Hari ini, kami akan menggunakan Next.js untuk membina borang hubungan e-mel.

Borang ialah bahagian penting dalam mana-mana tapak web, membenarkan pengguna berinteraksi dengan aplikasi. Sama ada untuk mendaftar, log masuk, memberi maklum balas atau mengumpul data, borang adalah penting untuk pengalaman pengguna. Tanpa borang, aplikasi tindanan penuh tidak akan dapat mengumpulkan dan memproses input pengguna dengan betul.

Dalam blog ini, saya akan menunjukkan kepada anda cara membuat borang hubungan e-mel menggunakan Next.js, Hantar Semula dan Zod (untuk pengesahan borang). Kami akan meliputi penyediaan projek, mereka bentuk borang, mengendalikan penyerahan borang dan mencipta laluan API yang berasingan. Pada akhirnya, anda akan tahu cara membina dan menambah borang pada apl Next.js anda, memastikan apl web anda berfungsi dengan baik dan mudah digunakan.

Jadi, tanpa berlengah lagi, mari kita mulakan.

Apakah Hantar Semula?

Hantar semula ialah API e-mel moden untuk pembangun. Ia direka bentuk untuk menjadikan penghantaran e-mel daripada aplikasi anda mudah, boleh dipercayai dan berskala. Tidak seperti perkhidmatan e-mel tradisional, Hantar Semula dibina dengan mengambil kira pembangun, menawarkan API mudah yang disepadukan dengan lancar dengan pelbagai bahasa pengaturcaraan dan rangka kerja, termasuk Next.js.

Dalam projek borang Next.js kami, kami akan menggunakan Hantar Semula untuk menghantar e-mel. Apabila pengguna menyerahkan borang, kami akan menggunakan API Hantar Semula untuk menghantar e-mel pengesahan atau memproses data borang seperti yang diperlukan.

Apa itu Zod?

Zod ialah alat yang berkuasa untuk data anda. Ia adalah perpustakaan pertama TypeScript yang membantu anda menentukan dan menyemak bentuk data anda. Anggap ia sebagai menetapkan peraturan untuk data anda dan kemudian pastikan data itu sepadan dengan peraturan tersebut sebelum anda menggunakannya.

How to Create Dynamic Email Contact Form in Next.js Using Resend and Zod

Jika anda menggunakan TypeScript (dan jika tidak, anda harus mempertimbangkannya!), Zod bermain dengan baik dengannya. Ia secara automatik boleh membuat kesimpulan jenis TypeScript daripada skema anda, yang merupakan penjimat masa yang besar. Walaupun TypeScript menyemak jenis pada masa kompilasi, Zod melakukannya pada masa jalan. Ini bermakna anda boleh menangkap isu data yang mungkin tergelincir melalui pemeriksaan jenis statik. Anda boleh menggunakan Zod untuk semua jenis senario pengesahan data, daripada input bentuk ringkas kepada respons API yang kompleks.

Persediaan Projek

Mari mulakan dengan menyediakan projek Next.js kami dengan semua kebergantungan yang diperlukan. Kami akan menggunakan TypeScript untuk keselamatan jenis, Tailwind CSS untuk penggayaan, Ant Design untuk komponen UI, Zod untuk pengesahan borang dan Hantar Semula untuk fungsi e-mel.

  • Buat projek Next.js baharu dengan TypeScript:
npx create-next-app@latest my-contact-form --typescript
cd my-contact-form
Salin selepas log masuk
  • Pasang kebergantungan tambahan:
yarn add antd zod resend react-icons
Salin selepas log masuk

Sediakan pembolehubah Persekitaran

Untuk menghantar e-mel, kami akan menggunakan Hantar Semula, jadi kami memerlukan kunci API Hantar Semula. Sebelum memulakan pelayan kami, mari pergi ke Hantar Semula dan dapatkan kunci API kami. Klik di sini untuk pergi ke tapak Hantar Semula dan klik butang log masuk.

How to Create Dynamic Email Contact Form in Next.js Using Resend and Zod

Selepas melog masuk, anda akan diubah hala ke halaman ini. Di sini, anda akan melihat semua e-mel yang anda terima daripada borang anda.

How to Create Dynamic Email Contact Form in Next.js Using Resend and Zod

Di sini, klik pada bahagian Kunci API

How to Create Dynamic Email Contact Form in Next.js Using Resend and Zod

Dan, jana kunci API dengan mengklik pada ini ? butang

How to Create Dynamic Email Contact Form in Next.js Using Resend and Zod

Sekarang, salin kunci API itu dan pastikan ia selamat. Seterusnya, buka VSCode dan buat fail baharu bernama .env dalam folder akar anda. Tambahkan pembolehubah persekitaran di sana.

RESEND_API_KEY=yourapikeywillbehere
Salin selepas log masuk

Kini anda juga boleh menjalankan pelayan anda menggunakan arahan ini.

yarn dev
Salin selepas log masuk
Salin selepas log masuk

Komponen Templat E-mel

Mari mulakan dengan mencipta templat e-mel. Ini akan menjadi templat yang anda terima apabila seseorang menghantar e-mel kepada anda melalui borang hubungan.

import * as React from 'react';

interface EmailTemplateProps {
  firstName: string;
  message: string;
}

export const EmailTemplate: React.FC<EmailTemplateProps> = ({
  firstName,
  message,
}) => (
  <div>
    <h1>Hello, I am {firstName}!</h1>
    <p>You have received a new message from your Portfolio:</p>
    <p>{message}</p>
  </div>
);
Salin selepas log masuk

Komponen React ringkas ini mentakrifkan struktur e-mel yang akan dihantar apabila seseorang menyerahkan borang hubungan. Ia memerlukan dua prop: nama pertama dan mesej. Komponen mencipta ucapan diperibadikan menggunakan nama pertama dan memaparkan mesej yang diserahkan.

Melaksanakan Penghantaran E-mel dengan Hantar Semula API

Di sini. kita akan melihat cara melaksanakan fungsi penghantaran e-mel menggunakan API Hantar Semula.

The Code Structure

First, let's look at where this code lives in our Next.js project:

app/
  ├── api/
  │   └── v1/
  │       └── send/
  │           └── route.ts
Salin selepas log masuk

This structure follows Next.js 13's App Router convention, where API routes are defined as route handlers within the app directory.

This is our complete API route code ?

import { EmailTemplate } from 'app/components/email-template';
import { NextResponse } from 'next/server';
import { Resend } from 'resend';
import { v4 as uuid } from 'uuid';

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

export async function POST(req: Request) {
  try {
    const { name, email, subject, message } = await req.json();

    const { data, error } = await resend.emails.send({
      from: 'Contact Form <onboarding@resend.dev>',
      to: 'katare27451@gmail.com',
      subject: subject || 'New Contact Form Submission',
      reply_to: email,
      headers: {
        'X-Entity-Ref-ID': uuid(),
      },
      react: EmailTemplate({ firstName: name, message }) as React.ReactElement,
    });

    if (error) {
      return NextResponse.json({ error }, { status: 500 });
    }

    return NextResponse.json({ data, message: 'success' }, { status: 200 });
  } catch (error) {
    console.error('Error processing request:', error);
    return NextResponse.json({ error: 'Failed to process request' }, { status: 500 });
  }
}
Salin selepas log masuk

Breaking Down the Code

Now, let's examine each part of the code:

import { EmailTemplate } from 'app/components/email-template';
import { NextResponse } from 'next/server';
import { Resend } from 'resend';
import { v4 as uuid } from 'uuid';
Salin selepas log masuk

These import statements bring in the necessary dependencies:

  • EmailTemplate: A custom React component for our email content(That we already built above.
  • NextResponse: Next.js utility for creating API responses.
  • Resend: The Resend API client.
  • uuid: For generating unique identifiers.
const resend = new Resend(process.env.RESEND_API_KEY);
Salin selepas log masuk

Here, we initialize the Resend client with our API key. It's crucial to keep this key secret, so we store it in an environment variable.

export async function POST(req: Request) {
  // ... (code inside this function)
}
Salin selepas log masuk

This exports an async function named POST, which Next.js will automatically use to handle POST requests to this route.

const { name, email, subject, message } = await req.json();
Salin selepas log masuk

We extract the form data from the request body. This assumes the client is sending a JSON payload with these fields.

const { data, error } = await resend.emails.send({
  from: 'Contact Form <onboarding@resend.dev>',
  to: 'katare27451@gmail.com',
  subject: subject || 'New Contact Form Submission',
  reply_to: email,
  headers: {
    'X-Entity-Ref-ID': uuid(),
  },
  react: EmailTemplate({ firstName: name, message }) as React.ReactElement,
});
Salin selepas log masuk

This is where we'll get our emails! We use Resend's send method to dispatch the email:

  • from: The sender's email address.
  • to: The recipient's email address.
  • subject: The email subject, using the provided subject or a default.
  • reply_to: Sets the reply-to address to the form submitter's email.
  • headers: Includes a unique ID for tracking.
  • react: Uses our custom EmailTemplate component to generate the email content.
if (error) {
  return NextResponse.json({ error }, { status: 500 });
}

return NextResponse.json({ data, message: 'success' }, { status: 200 });
Salin selepas log masuk

Here, we handle the response from Resend. If there's an error, we return a 500 status with the error details. Otherwise, we send a success response.

catch (error) {
  console.error('Error processing request:', error);
  return NextResponse.json({ error: 'Failed to process request' }, { status: 500 });
}
Salin selepas log masuk

This catch block handles any unexpected errors, logs them, and returns a generic error response.

And that's it! We've set up our API route. The only thing left is to set up our logic and UI. Let's do that too ?

Contact Page Component

In your app directory, create a new folder named contact-form and inside this folder, create a file named page.tsx.

app/
  ├── contact-form/
  │   └── page.tsx
Salin selepas log masuk

Imports and Dependencies

First, import all necessary components from Ant Design, Next.js, and React Icons. We also import Zod for form validation.

import React from 'react';
import { Form, Input, Button, message, Space, Divider, Typography } from 'antd';
import Head from 'next/head';
import { FaUser } from 'react-icons/fa';
import { MdMail } from 'react-icons/md';
import { FaMessage } from 'react-icons/fa6';
import { z } from 'zod';
import Paragraph from 'antd/es/typography/Paragraph';
Salin selepas log masuk

UI Layout and Design

Now, let's create our UI, and then we'll move on to the logic. Our form will look something like this.?

How to Create Dynamic Email Contact Form in Next.js Using Resend and Zod

In your page.tsx, after all the import statements, define a component and add a return statement first.

const ContactPage: React.FC = () => {
  return (
      <div className="max-w-3xl w-full space-y-8 bg-white p-10 rounded-xl shadow-2xl">
       /* our code will be here */
      </div>
  );
};

export default ContactPage;
Salin selepas log masuk

Currently, we have just a simple div with a few tailwind styling now, we'll first add our heading part.

How to Create Dynamic Email Contact Form in Next.js Using Resend and Zod

...

        <div>
          <h2 className="mt-1 text-center text-3xl font-extrabold text-gray-900">Get in Touch</h2>
          <p className="mt-1 mb-4 text-center text-sm text-gray-600">
            I'd love to hear from you! Fill out the form below to get in touch.
          </p>
        </div>

...
Salin selepas log masuk

Now, let's add our input fields

How to Create Dynamic Email Contact Form in Next.js Using Resend and Zod

 ...     
        <Form
          form={form}
          name="contact"
          onFinish={onFinish}
          layout="vertical"
          className="mt-8 space-y-6"
        >
          <Form.Item
            name="name"
            rules={[{ required: true, message: 'Please input your name!' }]}
          >
            <Input prefix={<FaUser className="site-form-item-icon" />} placeholder="Your Name" size="large" />
          </Form.Item>
          <Form.Item
            name="email"
            rules={[
              { required: true, message: 'Please input your email!' },
              { type: 'email', message: 'Please enter a valid email!' }
            ]}
          >
            <Input prefix={<MdMail className="site-form-item-icon" />} placeholder="Your Email" size="large" />
          </Form.Item>
          <Form.Item
            name="subject"
            rules={[{ required: true, message: 'Please input a subject!' }]}
          >
            <Input prefix={<FaMessage className="site-form-item-icon" />} placeholder="Subject" size="large" />
          </Form.Item>
          <Form.Item
            name="message"
            rules={[{ required: true, message: 'Please input your message!' }]}
          >
            <TextArea
              placeholder="Your Message"
              autoSize={{ minRows: 4, maxRows: 8 }}
            />
          </Form.Item>
          <Form.Item>
            <Button
              type="primary"
              htmlType="submit"
              className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
              disabled={isSubmitting}
            >
              {isSubmitting ? 'Sending...' : 'Send Message'}
            </Button>
          </Form.Item>
        </Form>
...

Salin selepas log masuk

Here, in the above code firstly we added a Form Component. This is the main Form component from Ant Design. It has the following props:

<Form
  form={form}
  name="contact"
  onFinish={onFinish}
  layout="vertical"
  className="mt-8 space-y-6"
>
  {/* Form items */}
</Form>
Salin selepas log masuk
  • form: Links the form to the form object created using Form.useForm().
  • name: Gives the form a name, in this case, "contact".
  • onFinish(we'll declare this function in our next section): Specifies the function to be called when the form is submitted successfully.
  • layout: Sets the form layout to "vertical".
  • className: Applies CSS classes for styling.

Then, we added a Form Items. Each Form.Item represents a field in the form. Let's look at the "name" field as an example.

<Form.Item
  name="name"
  rules={[{ required: true, message: 'Please input your name!' }]}
>
  <Input prefix={<FaUser className="site-form-item-icon" />} placeholder="Your Name" size="large" />
</Form.Item>
Salin selepas log masuk
  • name: Specifies the field name.
  • rules: An array of validation rules. Here, it's set as required.
  • The Input component is used for text input, with a user icon prefix and a placeholder.

Similarly, we have Email and other fields.

<Form.Item
  name="email"
  rules={[
    { required: true, message: 'Please input your email!' },
    { type: 'email', message: 'Please enter a valid email!' }
  ]}
>
  <Input prefix={<MdMail className="site-form-item-icon" />} placeholder="Your Email" size="large" />
</Form.Item>
Salin selepas log masuk

This field has an additional rule to ensure the input is a valid email address.

Subject and Message Fields: These are similar to the name field, with the message field using a TextArea component for multi-line input.

Then, we have a Submit Button to submit our form

<Form.Item>
  <Button
    type="primary"
    htmlType="submit"
    className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
    disabled={isSubmitting}
  >
    {isSubmitting ? 'Sending...' : 'Send Message'}
  </Button>
</Form.Item>
Salin selepas log masuk

This is the submit button for the form. It's disabled when isSubmitting (we'll add this state in our next section) is true, and its text changes to "Sending..." during submission.

Form Submission Logic

So, in the logic part, we have a few things to do:

  • Setting up Zod schema for form validation
  • Adding new states
  • and, implementing a onFinish function

We'll start with setting up our schema first.

// Zod schema for form validation
const contactSchema = z.object({
  name: z.string().min(4, 'Name must be at least 4 characters').max(50, 'Name must not exceed 50 characters'),
  email: z.string().email('Invalid email address').regex(/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/, "Email must be a valid format"),
  subject: z.string().min(5, 'Subject must be at least 5 characters').max(100, 'Subject must not exceed 100 characters'),
  message: z.string().min(20, 'Message must be at least 20 characters').max(1000, 'Message must not exceed 1000 characters'),
});

type ContactFormData = z.infer<typeof contactSchema>;
Salin selepas log masuk

This part defines a Zod schema for form validation. As we already learned, Zod is a TypeScript-first schema declaration and validation library. The contactSchema object defines the structure and validation rules for the contact form:

  • name: Must be a string between 4 and 50 characters.
  • email: Must be a valid email address and match the specified regex pattern.
  • subject: Must be a string between 5 and 100 characters.
  • message: Must be a string between 20 and 1000 characters.

The ContactFormData type is inferred from the Zod schema, providing type safety for the form data.

Now, let's add 2 new states and implement our onFinish function.

const ContactPage: React.FC = () => {
  const [form] = Form.useForm<ContactFormData>();
  const [isSubmitting, setIsSubmitting] = React.useState(false);

  const onFinish = async (values: ContactFormData) => {
    setIsSubmitting(true);
    try {
      contactSchema.parse(values);

      const response = await fetch('/api/v1/send', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(values),
      });

      if (!response.ok) {
        message.error('Failed to send message. Please try again.');
        setIsSubmitting(false); 
      }

      const data = await response.json();

      if (data.message === 'success') {
        message.success('Message sent successfully!');
        setIsSubmitting(false);
        form.resetFields();
      } else {
        throw new Error('Failed to send message');
      }
    } catch (error) {
      if (error instanceof z.ZodError) {
        error.errors.forEach((err) => {
          message.error(err.message);
          setIsSubmitting(false);
        });
      } else {
        message.error('Failed to send message. Please try again.');
        setIsSubmitting(false);
      }
    } finally {
      setIsSubmitting(false);
    }
  };
Salin selepas log masuk

This part defines the ContactPage functional component:

  • It uses the Form.useForm hook to create a form instance.
  • It manages a isSubmitting state to track form submission status.
  • The onFinish function is called when the form is submitted:
  1. It sets isSubmitting to true.
  2. It uses contactSchema.parse(values) to validate the form data against the Zod schema.
  3. If validation passes, it sends a POST request to /api/v1/send with the form data.
  4. It handles the response, showing success or error messages accordingly.
  5. If there's a Zod validation error, it displays the error message.
  6. Finally, it sets isSubmitting back to false.

This setup ensures that the form data is validated on both the client-side (using Antd's form validation) and the server-side (using Zod schema validation) before sending the data to the server. It also manages the submission state and provides user feedback through success or error messages.

And, this is the complete code of our contact-form file ?

"use client";

import React from 'react';
import { Form, Input, Button, message, Space, Divider, Typography } from 'antd';
import Head from 'next/head';
import { FaUser } from 'react-icons/fa';
import { MdMail } from 'react-icons/md';
import { FaMessage } from 'react-icons/fa6';
import { z } from 'zod';
import { Container } from 'app/components/container';
import Paragraph from 'antd/es/typography/Paragraph';

const { TextArea } = Input;
const { Text } = Typography;

// Zod schema for form validation
const contactSchema = z.object({
  name: z.string().min(4, 'Name must be at least 4 characters').max(50, 'Name must not exceed 50 characters'),
  email: z.string().email('Invalid email address').regex(/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/, "Email must be a valid format"),
  subject: z.string().min(5, 'Subject must be at least 5 characters').max(100, 'Subject must not exceed 100 characters'),
  message: z.string().min(20, 'Message must be at least 20 characters').max(1000, 'Message must not exceed 1000 characters'),
});

type ContactFormData = z.infer<typeof contactSchema>;

const ContactPage: React.FC = () => {
  const [form] = Form.useForm<ContactFormData>();
  const [isSubmitting, setIsSubmitting] = React.useState(false);

  const onFinish = async (values: ContactFormData) => {
    setIsSubmitting(true);
    try {
      contactSchema.parse(values);

      const response = await fetch('/api/v1/send', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(values),
      });

      if (!response.ok) {
        message.error('Failed to send message. Please try again.');
        setIsSubmitting(false); 
      }

      const data = await response.json();

      if (data.message === 'success') {
        message.success('Message sent successfully!');
        setIsSubmitting(false);
        form.resetFields();
      } else {
        throw new Error('Failed to send message');
      }
    } catch (error) {
      if (error instanceof z.ZodError) {
        error.errors.forEach((err) => {
          message.error(err.message);
          setIsSubmitting(false);
        });
      } else {
        message.error('Failed to send message. Please try again.');
        setIsSubmitting(false);
      }
    } finally {
      setIsSubmitting(false);
    }
  };

  return (
      

Get in Touch

I'd love to hear from you! Fill out the form below to get in touch.

} placeholder="Your Name" size="large" /> } placeholder="Your Email" size="large" /> } placeholder="Subject" size="large" />