用Notion API和Next.js创建联系表单

886 阅读10分钟

无论是你的投资组合网站还是SaaS产品网站,你都需要为访问者和潜在客户提供一个专门的联系页面,以便他们能够与你联系。通常情况下,这个页面包括一个表格,访客可以填写这个表格来发送信息或提出问题。但是,你在哪里以及如何存储这些回复?

为存储表单提交的唯一目的而建立一个数据库并不是最方便的选择。有了Next.jsNotion API,你可以使用数据库功能将所有提交的信息直接保存到你的Notion工作区。如果你已经把Notion作为一个项目管理工具,并且可以把所有的东西集中到一个地方,这就特别有用。

前提条件

你需要对Next.js(或React)有一个基本的了解来学习本教程。如果你没有使用Next.js,我们将在Next.js API路线中编写的所有代码也可以在你的Node.js服务器中使用。

在本教程中,我们将介绍。

  • 设置Notion数据库
  • 用Next.js建立一个联系页面
  • 用CSS模块设计页面的样式
  • 在Next.js上使用环境变量
  • 使用Next.js的API路由,通过SDK与Notion API进行交互

设置Notion数据库

首先,让我们设置Notion数据库,并创建一个新的集成,通过Notion API访问它。访问Notion网站,首先为数据库创建一个新页面。如果你以前没有使用过Notion,你可能会被提示登录。

Dashboard To Create A Contact Page With Table Database

用表数据库创建一个新的Notion页面

在屏幕的左下角,点击**"+新页面**",并给它起个你喜欢的名字。我现在把它命名为 "联系表格提交"。现在,点击数据库部分的 "",创建一个表格式的数据库,用于存储表单回复。

Dropdown Tab To Create Property Modifications To Database

你可以通过添加新的属性和/或编辑现有的属性来修改数据库以满足你的要求。下面是我们将在Notion数据库中存储的属性及其类型。

  1. 姓名:标题属性类型
  2. 电子邮件:电子邮件属性类型
  3. 目的:选择属性类型
  4. 消息:文本属性类型
  5. Contacted On: 创建一个时间属性类型,当表单提交被添加到表中时自动生成。

现在我们的Notion数据库已经准备好了,让我们创建一个新的集成,将数据库连接到我们的Next.js应用程序。

为Notion和Next.js创建一个集成

要创建一个新的集成,请到Notion的 "我的集成 "页面,点击**+新集成**。你需要填写基本信息,并选择你创建联系页面的工作空间。一旦你完成了,你会被转到一个类似这样的屏幕。

My Integrations Section In Notion Database

Notion集成屏幕

复制并保存内部集成令牌,因为我们以后与Notion API互动时需要它。不要与其他任何人分享这个令牌。

Search Bar to Connect Notion Database To Integration

将集成与页面相连

最后,为了将Notion数据库与我们新创建的集成连接起来,请返回到 "联系表单提交"页面,点击位于屏幕右上角的 "分享"。选择你的集成,并点击邀请,允许其编辑访问。

配置部分就到此为止。是时候继续编写代码了。

设置Next.js应用程序

你可以使用create-next-app 工具,通过在终端上运行这个命令,快速启动一个新的Next.js应用程序。

npx create-next-app
# or
yarn create next-app

接下来,在你喜欢的文本编辑器或IDE上打开项目文件夹。在运行Next.js开发服务器之前,我们先安装几个npm包。

我们将使用两个包:react-toastify ,用于在表单提交时显示敬酒通知;@notionhq/client ,用于使用Notion JavaScript SDK与Notion API交互。

npm install react-toastify @notionhq/client
# or
yarn add react-toastify @notionhq/client

在你的终端上运行npm run dev ,启动开发服务器。在访问http://localhost:3000,你会看到一个类似这样的屏幕。

Welcome Page Of Next.js Inviting Viewer To Get Started

Next.js的模板代码输出

建立和设计联系表格

让我们用带有表单的自定义联系页面取代create-next-app 工具生成的模板代码。打开该 **pages/index.js**文件并粘贴下面的内容。

import styles from '../styles/Home.module.css';

export default function Home() {
  return (
    <div className={styles.container}>
      <form className={styles.form}>
        <h1 className={styles.title}>React Out To Us</h1>
        <div>
          <label htmlFor="name">Full Name</label>
          <input
            type="text"
            id="name"
            name="name"
            placeholder="John Doe"
            required
          />
        </div>
        <div className={styles.inputs}>
          <div>
            <label htmlFor="email">E-Mail Address</label>
            <input
              type="email"
              name="email"
              placeholder="johndoe@example.io"
              required
            />
          </div>
          <div>
            <label htmlFor="purpose">Purpose</label>
            <select name="purpose" id="purpose">
              <option value="" selected disabled required>
                Select one
              </option>
              <option value="Web Development">Web Development</option>
              <option value="App Development">App Development</option>
              <option value="Query / Question">Query / Question</option>
              <option value="Feedback / Message">Feedback / Message</option>
            </select>
          </div>
        </div>
        <div>
          <label htmlFor="message">Message</label>
          <textarea
            name="message"
            id="message"
            rows="5"
            placeholder="Hi there!"
            required
          ></textarea>
        </div>
        <button className={styles.btn} type="submit">
          Submit
        </button>
      </form>
    </div>
  );
}

请随意修改输入,并确保你的Notion数据库也有同样的输入。现在让我们用一些CSS来装扮这个表单。

转到styles/Home.module.css ,用下面的样式替换该文件中的样式。

.container {
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: #0093e9;
  background-image: linear-gradient(160deg, #0093e9 0%, #80d0c7 100%);
}
.form {
  background: white;
  padding: 2rem 1.5rem;
  border-radius: 6px;
  box-shadow: 2px 4px 8px rgba(0, 0, 0, 0.1);
  max-width: 600px;
}
.title {
  margin: 0 0 1.5rem;
}
.form label {
  display: block;
  text-transform: uppercase;
  font-size: 0.9rem;
  margin-bottom: 0.5rem;
  color: #334155;
}
.form input,
.form select,
.form textarea {
  width: 100%;
  border: none;
  background: #f4f4f5;
  padding: 0.75rem 0.5rem;
  font-size: 1rem;
  margin-bottom: 1.25rem;
  border-radius: 4px;
}
.form input:focus,
.form select:focus,
.form textarea:focus {
  outline: 2px solid #0093e9;
}
.inputs {
  display: flex;
  justify-content: space-between;
}
.inputs div {
  flex: 1;
}
.inputs div:first-child {
  margin-right: 1rem;
}
.btn {
  background-color: #0093e9;
  background-image: linear-gradient(160deg, #0093e9 0%, #80d0c7 100%);
  padding: 0.5rem 1rem;
  border: none;
  color: white;
  font-size: 1rem;
  font-weight: bold;
  border-radius: 4px;
  box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.1);
  cursor: pointer;
}

就像输入一样,你可以玩玩这些样式,根据你的喜好定制它们。访问http://localhost:3000,你会看到一个与此类似的页面。

Contact Page With Blue Background

联系页面和表格

不错!我们还没有设置好。我们还没有设置输入状态和表单提交处理程序,但我们会在设置完API路由后再来讨论这个问题。

在环境变量中存储机密数据

把所有的敏感信息--比如你的Notion内部集成令牌和Notion数据库ID--存储在环境变量中是一个很好的做法,这样你可以在以后需要的时候轻松地改变它们,并防止它们暴露在浏览器中。

因此,在项目的根目录下创建一个名为.env.local 的新文件。在这个文件中,我们将存储NOTION_API_KEY 变量,这是内部集成令牌,以及NOTION_DATABASE_ID ,我们可以从Notion数据库的URL中获得。

到你的Notion数据库的URL可能看起来像这样

Highlighted Text In URL Of Contact Page

URL中的选择文本表示数据库ID

数据库ID是?v= 前面的字母数字,也就是491b722c931a42208cfff667dcb58a12

请确保使用你自己的集成令牌和数据库ID。下面是你的.env.local 文件的样子。

NOTION_API_KEY = secret_qidbxxxxxxxxxxxxxxxxxxxxMYitF6IM
NOTION_DATABASE_ID = 491b722c931a42208cfff667dcb58a12

通过Next.js的API路由与Notion API进行交互

转到pages/API 文件夹,将hello.js 文件重命名为submit-form.js 。这个文件的API路由将在http://localhost:3000/api/submit-form。我们将使用Notion的官方JavaScript SDK来与我们的Notion数据库进行交互。

首先,我们需要从@notionhq/client SDK包中导入Client ,并创建一个新的实例。这个实例接收一个对象,其auth 键的值设置为集成令牌,可以使用process.env.NOTION_API_KEY

在处理函数中,可以使用req.method 来访问HTTP请求方法。因为我们只预计来自Next.js前端的POST ,所以我们可以对其他类型的请求用405 Method Not Allowed

const { Client } = require('@notionhq/client');

const notion = new Client({
  auth: process.env.NOTION_API_KEY,
});

export default async function handler(req, res) {
  if (req.method !== 'POST') {
    return res
      .status(405)
      .json({ message: `${req.method} requests are not allowed` });
  }
  try {
    const { name, email, purpose, message } = JSON.parse(req.body);
    await notion.pages.create({
      parent: {
        database_id: process.env.NOTION_DATABASE_ID,
      },
      properties: {
        Name: {
          title: [
            {
              text: {
                content: name,
              },
            },
          ],
        },
        Email: {
          email: email,
        },
        Purpose: {
          select: {
            name: purpose,
          },
        },
        Message: {
          rich_text: [
            {
              text: {
                content: message,
              },
            },
          ],
        },
      },
    });
    res.status(201).json({ msg: 'Success' });
  } catch (error) {
    res.status(500).json({ msg: 'There was an error' });
  }
}

要向我们的Notion数据库添加一个新的表单响应,请使用notion.pages.create() 方法。如果你以前没有使用过Notion,值得注意的是,每个数据库条目都是Notion的一个页面。这个方法接收一个以parent 对象和properties 对象为参数的对象。

parent 对象中,将database_id 设置为你的Notion数据库的ID,可以通过process.env.NOTION_DATABASE_IDproperties 对象乍看起来很复杂,但属性值对象的文档中有各种属性类型的例子,如rich_text,number,title,select, 等等。

最后,将整个notion.pages.create() 方法包围在一个try…catch 块内,以捕捉错误并作出相应的反应。你的API路由现在已经准备好与你的Notion数据库互动了。

为联系表单添加功能

尽管联系表单在布局和设计方面已经准备好了,但我们还没有给它添加功能。现在让我们通过以下方式来实现。

  1. 使用useState() 钩子为表单输入添加状态
  2. 为表单创建一个提交处理程序,它将调用我们的API路由
  3. 根据响应情况显示敬酒通知

让我们在pages/index.js 中编写这些功能的代码。

// For handling input states
import { useState } from 'react';

// For display toasts  
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.min.css';

import styles from '../styles/Home.module.css';

export default function Home() {

  // Input states
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [purpose, setPurpose] = useState('');
  const [message, setMessage] = useState('');

  // Form submit handler
  const submitForm = async (e) => {
    e.preventDefault();
    const res = await fetch('http://localhost:3000/api/submit-form', {
      method: 'POST',
      body: JSON.stringify({ name, email, purpose, message }),
    });
    // Success if status code is 201
    if (res.status === 201) {
      toast('Thank you for contacting us!', { type: 'success' });
    } else {
      toast('Please re-check your inputs.', { type: 'error' });
    }
  };

  return (
    <div className={styles.container}>
      <ToastContainer />
      <form className={styles.form} onSubmit={submitForm}>
        <h1 className={styles.title}>React Out To Us</h1>
        <div>
          <label htmlFor="name">Full Name</label>
          <input
            type="text"
            id="name"
            name="name"
            placeholder="John Doe"
            value={name}
            onChange={(e) => setName(e.target.value)}
            required
          />
        </div>
        <div className={styles.inputs}>
          <div>
            <label htmlFor="email">E-Mail Address</label>
            <input
              type="email"
              name="email"
              placeholder="johndoe@example.io"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              required
            />
          </div>
          <div>
            <label htmlFor="purpose">Purpose</label>
            <select
              name="purpose"
              id="purpose"
              value={purpose}
              onChange={(e) => setPurpose(e.target.value)}
            >
              <option value="" disabled required>
                Select one
              </option>
              <option value="Web Development">Web Development</option>
              <option value="App Development">App Development</option>
              <option value="Query / Question">Query / Question</option>
              <option value="Feedback / Message">Feedback / Message</option>
            </select>
          </div>
        </div>
        <div>
          <label htmlFor="message">Message</label>
          <textarea
            name="message"
            id="message"
            rows="5"
            placeholder="Hi there!"
            value={message}
            onChange={(e) => setMessage(e.target.value)}
            required
          ></textarea>
        </div>
        <button className={styles.btn} type="submit">
          Submit
        </button>
      </form>
    </div>
  );
}

对于处理输入状态,我们可以使用useState() 钩子,并相应地给每个输入分配一个value 和一个onChange 处理程序,使其成为一个受控输入。

现在创建一个名为submitForm 的异步函数,使用fetch() ,向我们位于http://localhost:3000/api/submit-form的API路由发出POST 请求。输入状态可以在正文中发送。

一旦请求被发出,我们可以检查请求的状态代码。状态代码201 表示响应已经成功添加到Notion数据库中。否则,会产生状态代码500 ,表示有错误。

对于添加祝词,从toast() 函数和ToastContainer 组件导入react-toastify 。此外,从react-toastify/dist/ReactToastify.min.css 中导入CSS文件,使其样式化。

<ToastContainer /> 组件添加到JSX中,并使用toast() 函数根据从submitForm 函数中的API收到的响应来分配祝酒词。

测试表单

让我们测试一下我们的联系表单,看看它是否工作。用适当的输入填写表格。你会注意到在提交表单时有一个成功的祝酒词。

Contact Form Submitted With A Green Confirmation Message

成功的表单提交

在提交一个不正确的反应时,例如一个无效的目的,错误的祝酒词会弹出来。

Contact Form Attempted To Submit With Red Unsuccessful Messages

不成功的表单提交

你可以用你的Notion数据库来验证这一点,以确认响应是按照预期存储的。

Dashboard In Notion Showing Responses From Users

Notion中的表单响应

总结

Notion对于团队和个人来说都是一个强大的工具。有了Notion的API,你永远不需要建立一个数据库或使用第三方服务来管理你的联系表单回复。你甚至可以与你的团队分享,并以多种方式将回复可视化--免费的我希望你喜欢这个教程。

The postCreating contact forms with the Notion API and Next.jsappeared first onLogRocket Blog.