Airbnb的克隆,增加了一个新的房子

198 阅读5分钟

这篇文章是一个新系列的一部分,我们用Next.js建立一个克隆的Airbnb。请看第一篇文章

在这一课中,我想添加添加一个新房子的功能。

在上一课中,我添加了一个链接到/host/new URL,当时我们还没有房子。

我也要把链接添加到components/Header.js 文件中,以使它容易找到,尽管在一个真正的应用程序中,你会把它移到不太显眼的地方,我想,因为这不是一个被多次使用的功能。

components/Header.js

<li>
  <Link href='/host/new'>
    <a>Add House</a>
  </Link>
</li>

现在创建一个新文件pages/host/new.js

在这个文件中,我们将创建一个表单来添加一个新的房子。

我们开始,通过添加一个房屋字段,title ,然后我们把表单提交给服务器,在host/new 服务器端路由。

import { useState } from 'react'
import Head from 'next/head'

import Layout from '../../components/Layout'

const NewHouse = () => {
  const [title, setTitle] = useState('')

  return (
    <Layout
      content={
        <div>
          <Head>
            <title>Add a new house</title>
          </Head>

          <form
            onSubmit={async (event) => {
              event.preventDefault()
              try {
                const response = await axios.post('/api/host/new', {
                  house: {
                    title,
                  },
                })
                if (response.data.status === 'error') {
                  alert(response.data.message)
                  return
                }

                console.log(response)
                goto('/host')
              } catch (error) {
                alert(error.response.data.message)
                return
              }
            }}
          >
            <input
              id='title'
              type='text'
              placeholder='House title'
              onChange={(event) => setTitle(event.target.value)}
            />
            <button>Add house</button>
          </form>

          <style jsx>{``}</style>
        </div>
      }
    />
  )
}

export default NewHouse

我们使用钩子来存储我们表单中每个项目的状态。

现在在server.js 文件中创建一个新的端点POST/api/host/new ,它使用Sequelize模型将房子添加到数据库中。

server.post('/api/host/new', async (req, res) => {
  const houseData = req.body.house

  if (!req.session.passport) {
    res.writeHead(403, {
      'Content-Type': 'application/json',
    })
    res.end(
      JSON.stringify({
        status: 'error',
        message: 'Unauthorized',
      })
    )

    return
  }

  const userEmail = req.session.passport.user
  User.findOne({ where: { email: userEmail } }).then((user) => {
    houseData.host = user.id
    House.create(houseData).then(() => {
      res.writeHead(200, {
        'Content-Type': 'application/json',
      })
      res.end(JSON.stringify({ status: 'success', message: 'ok' }))
    })
  })
})

我基本上是检查用户是否登录,然后将其id ,然后再存储到房屋数据中。

如果你现在尝试,在终端你会从Sequelize得到一长串错误。

Unhandled rejection SequelizeValidationError: notNull Violation: house.picture cannot be null,
notNull Violation: house.type cannot be null,
notNull Violation: house.town cannot be null,
notNull Violation: house.price cannot be null,
notNull Violation: house.superhost cannot be null,
notNull Violation: house.guests cannot be null,
notNull Violation: house.bedrooms cannot be null,
notNull Violation: house.beds cannot be null,
notNull Violation: house.baths cannot be null,
notNull Violation: house.wifi cannot be null,
notNull Violation: house.kitchen cannot be null,
notNull Violation: house.heating cannot be null,
notNull Violation: house.freeParking cannot be null,
notNull Violation: house.entirePlace cannot be null
//...

因为我们在数据库中把这些字段设置成了NOT NULL。

现在我们有了一个基本的表单设置,让我们添加所有我们需要的字段以避免这个错误

import { useState } from 'react'
import Head from 'next/head'
import axios from 'axios'
import Router from 'next/router'

import Layout from '../../components/Layout'

const NewHouse = () => {
  const [title, setTitle] = useState('')
  const [town, setTown] = useState('')
  const [price, setPrice] = useState(0)
  const [picture, setPicture] = useState('')
  const [description, setDescription] = useState('')
  const [guests, setGuests] = useState(0)
  const [bedrooms, setBedrooms] = useState(0)
  const [beds, setBeds] = useState(0)
  const [baths, setBaths] = useState(0)
  const [wifi, setWifi] = useState(false)
  const [kitchen, setKitchen] = useState(false)
  const [heating, setHeating] = useState(false)
  const [freeParking, setFreeParking] = useState(false)
  const [entirePlace, setEntirePlace] = useState(false)
  const [type, setType] = useState('Entire house')

  const houseTypes = ['Entire house', 'Room']

  return (
    <Layout
      content={
        <div>
          <Head>
            <title>Add a new house</title>
          </Head>

          <form
            onSubmit={async (event) => {
              event.preventDefault()
              try {
                const response = await axios.post('/api/host/new', {
                  house: {
                    title,
                    town,
                    price,
                    picture,
                    description,
                    guests,
                    bedrooms,
                    beds,
                    baths,
                    wifi,
                    kitchen,
                    heating,
                    freeParking,
                    entirePlace,
                    type,
                  },
                })
                if (response.data.status === 'error') {
                  alert(response.data.message)
                  return
                }

                Router.push('/host')
              } catch (error) {
                alert(error.response.data.message)
                return
              }
            }}
          >
            <p>
              <label>House title</label>
              <input
                required
                onChange={(event) => setTitle(event.target.value)}
                type='text'
                placeholder='House title'
              />
            </p>
            <p>
              <label>Town</label>
              <input
                required
                onChange={(event) => setTown(event.target.value)}
                type='text'
                placeholder='Town'
              />
            </p>
            <p>
              <label>Price per night</label>
              <input
                required
                onChange={(event) => setPrice(event.target.value)}
                type='number'
                placeholder='Price per night'
                value={price}
              />
            </p>
            <p>
              <label>House picture URL</label>
              <input
                required
                onChange={(event) => setPicture(event.target.value)}
                type='text'
                placeholder='House picture URL'
              />
            </p>
            <p>
              <label>House description</label>
              <textarea
                required
                onChange={(event) => setDescription(event.target.value)}
              ></textarea>
            </p>

            <div>
              <div>
                <p>
                  <label>Number of guests</label>
                  <input
                    required
                    onChange={(event) => setGuests(event.target.value)}
                    type='number'
                    placeholder='Number of guests'
                    value={guests}
                  />
                </p>
                <p>
                  <label>Number of bedrooms</label>
                  <input
                    required
                    onChange={(event) => setBedrooms(event.target.value)}
                    type='number'
                    placeholder='Number of bedrooms'
                    value={bedrooms}
                  />
                </p>
                <p>
                  <label>Number of beds</label>
                  <input
                    required
                    onChange={(event) => setBeds(event.target.value)}
                    type='number'
                    placeholder='Number of beds'
                    value={beds}
                  />
                </p>
                <p>
                  <label>Number of baths</label>
                  <input
                    required
                    onChange={(event) => setBaths(event.target.value)}
                    type='number'
                    placeholder='Number of baths'
                    value={baths}
                  />
                </p>
              </div>

              <div>
                <p>
                  <label>Does it have Wifi?</label>
                  <select
                    onChange={(event) => setWifi(event.target.value)}
                    value={wifi}
                  >
                    <option value='true'>Yes</option>
                    <option value='false'>No</option>
                  </select>
                </p>
                <p>
                  <label>Does it have a kitchen?</label>
                  <select
                    onChange={(event) => setKitchen(event.target.value)}
                    value={kitchen}
                  >
                    <option value='true'>Yes</option>
                    <option value='false'>No</option>
                  </select>
                </p>
                <p>
                  <label>Does it have heating?</label>
                  <select
                    onChange={(event) => setHeating(event.target.value)}
                    value={heating}
                  >
                    <option value='true'>Yes</option>
                    <option value='false'>No</option>
                  </select>
                </p>
                <p>
                  <label>Does it have free parking?</label>
                  <select
                    onChange={(event) => setFreeParking(event.target.value)}
                    value={freeParking}
                  >
                    <option value='true'>Yes</option>
                    <option value='false'>No</option>
                  </select>
                </p>
                <p>
                  <label>Is it the entire place?</label>
                  <select
                    onChange={(event) => setEntirePlace(event.target.value)}
                    value={entirePlace}
                  >
                    <option value='true'>Yes</option>
                    <option value='false'>No</option>
                  </select>
                </p>
                <p>
                  <label>Type of house</label>
                  <select
                    onChange={(event) => setType(event.target.value)}
                    value={type}
                  >
                    {houseTypes.map((item, key) => (
                      <option value={item} key={key}>
                        {item}
                      </option>
                    ))}
                  </select>
                </p>
              </div>
            </div>

            <button>Add house</button>
          </form>

          <style jsx>{`
            input[type='number'],
            select,
            textarea {
              display: block;
              padding: 20px;
              font-size: 20px !important;
              width: 100%;
              border: 1px solid #ccc;
              border-radius: 4px;
              box-sizing: border-box;
              margin-bottom: 10px;
            }
          `}</style>
        </div>
      }
    />
  )
}

export default NewHouse

我导入了Next.js Router。

import Router from 'next/router'

以便在表单成功提交后,我们可以调用Router.push('/host') ,将用户重定向到房屋列表中。

我在一次选择中使用了houseTypes 数组,向你展示如何动态地将数据添加到表单中。

房屋图片是一个路径,可以是绝对的URL,也可以是相对于基本路径的。

我还把所有的字段设置为required ,在前端,所以我们得到了浏览器的自动验证。

很好!现在表单应该很好地显示出来了。现在表单应该很好地显示出来了。

下一部分。克隆Airbnb,编辑房屋