使用XATA和Cloudinary构建与NextJ的联系电话目录
#javascript #网络开发人员 #教程 #xata

您可以使用联系电话目录管理和存储连接的名称,地址和电话号码。电话目录通过更轻松地找到特定的联系来节省呼叫者的时间和精力。

本文讨论了如何使用Next.js,Cloudinary和XATA创建联系电话目录应用程序。 Cloudinary是一个基于云的视频和图像管理平台,可为Web应用程序维护和转换上传的媒体提供服务。 Xata是一个Serverless Data平台,通过提供内置强大的搜索和分析的传统数据库的功能来简化开发人员如何与数据合作。

演示和源代码

查看完整的源代码here并访问实时演示here

先决条件

了解本文需要以下内容:

  • node.js的安装
  • javascript和react.js的知识
  • 一个云帐户(注册here
  • XATA帐户(注册here
  • 对造型的Tailwind CSS的理解

安装和设置

在您的终端上运行此命令以创建下一个应用程序。

npx create-next-app <app-name>

成功安装后,导航到创建的文件夹并在port 3000上启动服务器,并带有以下命令:

cd <app-name>
npm run dev

创建XATA数据库

创建帐户并登录XATA后,通过单击添加数据库加上ICON 。

create xata database

接下来,创建一个名为“触点”的表,如下图所示,其中包含以下字段:位置 firstName lastname phonenumber 电子邮件 imageurl id - 由XATA自动生成。

xata schema

设置XATA实例

要在项目中使用XATA,您必须在全球范围内首先安装XATA软件开发套件(SDK)以使用所有XATA命令。使用以下命令在全球安装XATA CLI:

npm i -g @xata.io/cli

接下来,运行以下命令进行身份验证:

xata auth login

上面的命令通过终端中的提示为您提供两种选择,您应该通过打开浏览器来创建新的现有API键,这将使您重定向到另一个浏览器,给予API名称,然后单击创建API键按钮。

接下来,使用以下命令使用XATA初始化项目。该命令为您提供一系列成功完成XATA设置的提示。

xata init

xata init

尾风设置

在您的项目中安装tailwind CSS,其中包括以下命令:

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

命令将在您的项目的根目录中创建两个文件,tailwind.config.js,postcss.config.js

在您的tailwind.config.js中,将路径添加到所有模板文件中的路径。

 module.exports = {
      content: [
        "./pages/**/*.{js,ts,jsx,tsx}",
        "./components/**/*.{js,ts,jsx,tsx}",
      ],
      theme: {
        extend: {},
      },
      plugins: [],
 }

接下来,将tailwind层中的每个层添加到styles/global.css file

 @tailwind base;
 @tailwind components;
 @tailwind utilities;

云设置

使用以下命令向您的项目添加云依赖性:

npm i @cloudinary/url-gen @cloudinary/react

Cloudinary上传小部件是一个交互式用户界面,使用户可以将文件从各种来源上传到您的应用程序。您将使用上传小部件将图像添加到XATA数据库中。要使用小部件,您必须在pages/contact/add-new-contact.js file head 部分中包含Cloudinariony widgation Javascript文件。

//pages/contact/add-new-contact.js
import Head from "next/head";
<Head>
    <script
      src='https://upload-widget.cloudinary.com/global/all.js'
      type='text/javascript'
    />
</Head>

设置使用上传预设
设置窗口小部件 上传预设使您能够集中定义一组资产上传选项,而不是在每个上传调用中指定它们。要创建上传预设,请导航到您的Cloudinary Console,然后单击设置选项卡。
接下来,单击上传选项卡,然后向下滚动到页面的上载预设部分。

cloudinary console

单击添加上传预设或使用上传预设名称 cloudinary,您需要重命名上传预设并更改签名模式通过单击编辑更改选项并保存按钮以实现更改。

upload preset page

接下来,复制上传预设名称并将其添加到您的代码中。创建一个openupWidget()函数来添加和打开小部件。在您的pages/contact/add-new-contact.js file中,粘贴以下代码:

//pages/contact/add-new-contact.js 

const openupWidget = () => {
    window.cloudinary
      .openUploadWidget(
        { cloud_name: "Your Cloud Name", upload_preset: "ml_default" },
        (error, result) => {
          if (!error && result && result.event === "success") {
            setImageUrl(result.info.url);
          }
        }
      )
      .open();
  };

在上面的代码块中:

  • openUploadWidget()方法接收两个参数,即您的cloud_name和您的上传预设。要获取您的cloud_name,请检查Cloudinary Dashboard
  • 取决于上传是否成功,图片URL或检测到的错误已登录到控制台

在您的“上传图像”按钮上,将openupWidget()代码添加到onClick事件侦听器中。单击按钮时,onClick事件侦听器启动您的openupWidget()函数。

<button
  className='bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded'
  type='button'
  onClick={openupWidget}
>
  Upload Image
</button>

保存电话目录的新联系人

创建一个表格来收集联系信息并在pages/contact/add-new-contact.js文件中的目录中添加新联系人。


/* eslint-disable @next/next/no-sync-scripts */
...
import Link from "next/link";
import { useRouter } from "next/router";
...

const AddContact = () => {
  const router = useRouter();
  const [firstName, setFirstName] = useState("");
  const [lastName, setLastName] = useState("");
  const [phoneNumber, setPhoneNumber] = useState();
  const [location, setLocation] = useState("");
  const [email, setEmail] = useState("");
  const [imageUrl, setImageUrl] = useState("");

 //open openupWidget function

  const handleSubmit = (e) => {
    e.preventDefault();
    fetch("/api/add-contact", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        firstName,
        lastName,
        phoneNumber,
        location,
        email,
        imageUrl: imageUrl,
      }),
    }).then(() => router.push("/"));
  };


  return (
    <>
      ...
      <section className='mx-auto w-[50%]'>
        <form>
          <p className='mt-[30px] mb-[30px] font-medium'>Add New Contact</p>
          <div className='mb-5'>
            <label htmlFor='firstName' className='block'>
              First Name
            </label>
            <input
              type='text'
              name='firstName'
              onChange={(e) => setFirstName(e.target.value)}
              required
              className='border-2 p-1 w-full mt-1'
            />
          </div>
          <div className='mb-5'>
            <label htmlFor='lastName' className='block'>
              Last Name
            </label>
            <input
              type='text'
              name='lastName'
              id='lastName'
              required
              onChange={(e) => setLastName(e.target.value)}
              className='border-2 p-1 w-full mt-1'
            />
          </div>
          <div className='mb-5'>
            <label htmlFor='phoneNumber' className='block'>
              Phone Number
            </label>
            <input
              type='text'
              name='phoneNumber'
              id='phoneNumber'
              required
              onChange={(e) => setPhoneNumber(e.target.value)}
              className='border-2 p-1 w-full mt-1'
            />
          </div>
          <div className='mb-5'>
            <label htmlFor='location' className='block'>
              Location
            </label>
            <input
              type='text'
              name='location'
              id='location'
              required
              onChange={(e) => setLocation(e.target.value)}
              className='border-2 p-1 w-full mt-1'
            />
          </div>
          <div className='mb-5'>
            <label htmlFor='email' className='block'>
              Email Address
            </label>
            <input
              type='email'
              name='email'
              id='email'
              required
              onChange={(e) => setEmail(e.target.value)}
              className='border-2 p-1 w-full mt-1'
            />
          </div>
          <div className='mb-5'>
            <button
              className='bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded'
              type='button'
              onClick={openupWidget}
            >
              Upload Image
            </button>
          </div>
          <div className="flex justify-end">
          <button
            type='submit'
            onClick={handleSubmit}
            className='bg-blue-600 text-gray-50 py-2 px-6 tracking-wide whitespace-nowrap  mb-10'
          >
            Submit
          </button>
          </div>

        </form>
      </section>
       ...
    </>
  );
};
export default AddContact;

在上面的代码中,您进行了以下操作:

  • 导入必要的依赖项和组件
  • 创建了第9-14行上输入的初始变量
  • 创建了名字的输入字段姓电子邮件地址上传图像按钮提交按钮在行46-121
  • 将所有输入值提交到XATA数据库,并以第18-34行重定向到主页

将联系信息添加到数据库

pages/api/add-contact.js内部粘贴以下代码:

//pages/api/add-contact.js 
import { getXataClient } from "../../src/xata";
const xata = getXataClient();
const handler = async (req, res) => {
  const { firstName, lastName, phoneNumber, location, email, imageUrl } =
    req.body;
  await xata.db.contacts.create({
    firstName,
    lastName,
    phoneNumber,
    location,
    email,
    imageUrl,
  });
  res.end();
};
export default handler;

在上面的代码块中,您进行了以下操作:

  • 从初始化过程中创建的xata文件导入了getXataClient,还使用getXataClient对象创建了一个新实例
  • 获得了firstNamelastNamephoneNumberlocationemailimageUrl变量来自请求正文以添加到数据库
  • 在XATA create方法中添加了变量,以在数据库上创建数据。将变量作为参数传递时,重要的是要注意以下内容:
    • create()contacts对象上,因为您的数据库的名称为contacts
    • 变量与数据库上的表相同
  • 将结果数据保存在数据库中后,将结果数据发送到客户端

从数据库获取联系信息

要在主页上显示所有联系人信息,请首先创建您的components/LandingPage.js文件,然后添加以下代码段:

//components/LandingPage.js
import Footer from "./Footer";
import Header from "./Header";
const LandingPage = ({ children }) => {
  return (
    <div className='min-h-screen flex flex-col'>
      <div className='border-b border-gray-100'>
        <Header />
      </div>
      <main className='flex-grow   md:max-w-7xl md:mx-auto md:w-4/5 my-5 '>
        {children}
      </main>
      <div className='border-t border-gray-100'>
        <Footer />
      </div>
    </div>
  );
};
export default LandingPage;

您需要从src/xata.js文件导入getXataClient。创建一个pages/index.js文件并添加以下代码:

//pages/index.js

import Head from "next/head";
import { useState } from "react";
import LandingPage from "../components/LandingPage";
import { getXataClient } from "../src/xata";
import SearchList from "../components/SearchList/SearchList";
import AllContactsList from "../components/AllContactsLists/AllContactsLists";

export default function Home({ phoneBooks }) {
  const [searchWord, setSearchWord] = useState();
  const [searchContacts, setSearchContacts] = useState();

  const handleSearch = async (e) => {
    e.preventDefault();
  };
  return (
    <div>
      <Head>
        <title>Contacts Phone Directory</title>
        <meta name='description' content='Job board app' />
        <link rel='icon' href='/favicon.ico' />
      </Head>
      <LandingPage>
        <div className='flex justify-center mb-4'>
          <input
            className='placeholder:italic placeholder:text-slate-400 block bg-white w-[50%] border border-slate-300 rounded-md py-2 pl-9 pr-3 shadow-sm focus:outline-none focus:border-sky-500 focus:ring-sky-500 focus:ring-1 sm:text-sm'
            placeholder='Search for contact...'
            type='text'
            onChange={(e) => setSearchWord(e.target.value)}
          />
          <button
            className='bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded'
            type='submit'
            onClick={handleSearch}
          >
            Search
          </button>
        </div>
        {searchContacts ? <SearchList allContacts={searchContacts} /> : ""}
        {searchContacts ? "" : <AllContactsList allContacts={phoneBooks} />}
      </LandingPage>
    </div>
  );
}
export async function getServerSideProps() {
  const xata = getXataClient();
  const phoneBooks = await xata.db.contacts.getAll();
  return { props: { phoneBooks } };
}

在上面的代码块中,您进行了以下操作:

  • src/xata.js文件中导入getXataClient,以查询XATA的所有记录,并将数据作为电话簿Prop返回到AllContactsListSearchList
  • 使用有条件的语句渲染AllContactsListSearchList作为pages/index.js FILE内部的Landingpage的儿童道具
  • 创建的输入搜索字段以搜索联系信息
  • 已声明的变量可存储searchWordsearchContacts的初始状态
  • 将着陆页组件传递给components/LandingPage.js文件

在数据库中搜索联系信息

使用下面的代码更新searchWord searchWord函数。

//pages/index.js  
const handleSearch = async (e) => {
    e.preventDefault();
    const result = await fetch("/api/search-contact", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        searchWord,
      }),
    }).then((r) => r.json());
    setSearchContacts(result);
  };

在上面的代码中,您进行了以下操作:

  • 使用XATA的REST API
  • 进行搜索请求
  • 在搜索字段中检索输入值并将其传递给请求正文。
  • 通过XATA数据库的搜索单词的结果更新了searchContacts

接下来,在pages/api文件夹中创建search-contact.js文件。确保使用用于在第3行(“ pages/api/search-contact.js”)上提出搜索请求的API端点的创建文件的名称
将下面的代码复制并粘贴到pages/api/search-contact.js文件

//pages/api/search-contact.js
import { getXataClient } from '../../src/xata';
const xata = getXataClient();
const handler = async (req, res) => {
  const { searchWord } = req.body;
  const results = await xata.search.all(searchWord);
  res.send(results);
};
export default handler;

在上面的代码块中,您进行了以下操作:

  • xata文件导入getXataClient,还使用getXataClient对象创建了一个新实例
  • 从输入字段中获取searchWord变量,并使用它是否存在XATA数据库搜索。
  • 将结果数据保存在数据库中后,将结果数据发送到客户端

接下来,将代码复制并粘贴到components/SearchList/SearcList.js

内的github要点

https://gist.github.com/fatima-ola/00541b18715247fc7d7b23b84d4b5914

在上面的代码块中,您进行了以下操作:

  • 检索传递给SearchList组件的allContacts道具
  • 将其提取到一组对象中
  • 通过contactInfo数组映射并显示搜索的信息

单联系列表创建

要获取有关单击点击页面上显示的单个联系人的更多信息,请在pages目录中的一个名为contact的文件夹中创建一个新文件[id].js。粘贴以下代码:

//pages/contact/[id].js
import { getXataClient } from "../../src/xata";
import Footer from "../../components/Footer";
import Header from "../../components/Header";
import Image from "next/image";
import { useRouter } from "next/router";
import { MdWifiCalling3 } from "react-icons/md";
import { BiMessageDetail } from "react-icons/bi";
import { BiVideo } from "react-icons/bi";
const Contact = ({ data }) => {
  const router = useRouter();

  const deleteContact = (id) => {
    });
  };
  function getFirstLetter(character) {
    return character.charAt(0).toUpperCase();
  }

  return (
    <div className='min-h-screen flex flex-col'>
      <div className='border-b border-gray-100 mb-6'>
        <Header />
      </div>
      <section className='mx-auto w-[30%] sm:w-[50%] xs:w-[60%] p-8 shadow-sm border-2 hover:shadow-lg rounded-lg'>
        <div className='flex justify-center'>
          {data.imageUrl === "" ? (
            <p className='self-center bg-pink-200 font-bold py-2 px-4 mr-2 rounded-full'>
              {getFirstLetter(data.firstName)}
            </p>
          ) : (
            <Image
            width={80}
            height={20}
            className='h-20 w-30 rounded-full'
            src={data.imageUrl}
            alt=''
          />
          )}
        </div>
        <div className='text-center font-semibold'>
          <p>{`${data.firstName} ${data.lastName}`}</p>
        </div>
        <div className='flex justify-center'>
          <ul className='list-none flex m-4'>
            <li className='p-3 text-blue-600 text-[22px]'>
              <MdWifiCalling3 className='cursor-pointer' />
            </li>
            <li className='p-3  text-red-400 text-[22px]'>
              <BiMessageDetail className='cursor-pointer' />
            </li>
            <li className='p-3 text-purple-400 text-[22px]'>
              <BiVideo className='cursor-pointer' />
            </li>
          </ul>
        </div>
        <div className=''>
          <div className='my-3'>
            <p className='font-semibold'>Mobile:</p>
            <p>{data.phoneNumber}</p>
          </div>
          <div className='my-3'>
            <p className='font-semibold'>Location:</p>
            <p>{data.location}</p>
          </div>
          <div className='my-3'>
            <p className='font-semibold'>Email:</p>
            <p className="">{data.email}</p>
          </div>
        </div>
        <div className='flex justify-center my-6'>
          <button
            type='submit'
            className=' bg-red-300 text-gray-50 hover:bg-red-600 py-2 px-6 rounded-full'
            onClick={() => deleteContact(data.id)}
          >
            Delete
          </button>
        </div>
      </section>
      <div className='border-t border-gray-100 mt-6'>
        <Footer />
      </div>
    </div>
  );
};
export default Contact;
export async function getServerSideProps(context) {
  const { id } = context.query;
  const xata = await getXataClient();
  const contact = await xata.db.contacts.read(id);
  return {
    props: {
      data: contact,
    },
  };
}

上面的代码利用了带有上下文参数的getServerSideProps,并且此对象将使用查询字符串。之后,该功能返回道具读取id的动态路线,并使用read()从XATA获得一张记录。
单击其中一个联系人列表打开一个带有ID的联系URL的新页面:http://localhost:3000/contact/rec_cdt5k7ft80rj82cd2kpg

从数据库中删除联系信息

创建一个pages/api/delete-contact.js文件,并在下面添加此代码以使用从请求正文中获得的id从数据库中删除联系人:

import { getXataClient } from '../../src/xata';
const xata = getXataClient();
const handler = async (req, res) => {
  const { id } = req.body;
  await xata.db.contacts.delete(id);
  res.end();
};
export default handler;

接下来,使用下面的代码更新pages/contact/[id].js文件中的deleteContact()函数。

const deleteContact = (id) => {
    fetch("/api/delete-contact", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ id: id }),
    }).then(() => {
      router.push("/");
    });
  };

在上面的代码中,您执行了以下

  • 创建一个函数来查询您的/api/delete-product端点。
  • 通过了要删除作为参数的产品的id/api/delete-product处理程序需要id来查找并成功删除联系人。删除产品后,您可以返回到着陆页。

最后,这就是您的最终应用程序的外观:

final result
https://www.loom.com/share/9546ccd0a23c4a02ad6c22bb681a2cfe

结论

在本文中,您学会了如何使用XATA和Cloudinary构建可以在目录中添加新联系人的联系电话目录,查看单个联系人,搜索联系人并删除联系人。

资源
-Xata Documentation
-Cloudinary Upload Preset
-Setup Upload Widget