Airbnb的克隆—编辑房屋

146 阅读4分钟

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

在上一课中,我们让人们添加一个新的房子。在这一课中,我们将让他们编辑他们已经添加的房子。

创建一个名为pages/host/[id].js 的文件。

在那里,我们将创建编辑房屋的表格。

就像我们在pages/houses/[id].js ,我们可以在getInitialProps 函数中得到房子的数据,在那里我们将在query 值中得到房子的id

现在我们有了这些信息,我们可以向服务器要房子的数据。我们将重新使用我们已经创建的一个端点,它监听/api/houses/:id

pages/host/[id].js

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

const EditHouse = props => {
  return <Layout content={<div>{props.house.title}</div>} />
}

EditHouse.getInitialProps = async ({ query }) => {
  const { id } = query
  const response = await axios.get(`http://localhost:3000/api/houses/${id}`)

  return {
    house: response.data
  }
}

export default EditHouse

我们导入axios ,并在getInitialProps 内调用/api/houses/${id} ,将我们从该端点得到的数据分配给house

很好!现在我们可以将表单添加到模板中。

我已经预见到这将与我们在pages/host/new.js 中定义的表格有很多相似之处,所以最好将其提取到自己的组件中,我们将其存入components/HouseForm.js 。在我看来,两次使用完全相同的标记是非常糟糕的,这也是引入一个通用组件的完美地方,在这里可以提取这个HTML。

在这个文件中,我们通过props获得房子的数据,如果这个被pages/host/[id].js 组件加载,也就是我们用来编辑房子的那个,这个对象就会出现。

否则,对于pages/host/new.js ,将没有道具,我们将像以前一样使用默认值。

我们还向组件传递了一个edit 道具,所以我们知道它是在编辑模式下,还是在 "新 "模式下。

我还利用这个机会,用一个网格使表单看起来更漂亮。

这就是它。

components/HouseForm.js

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

const HouseForm = props => {
  const id = (props.house && props.house.id) || null

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

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

  return (
    <div>
      <form
        onSubmit={async event => {
          event.preventDefault()
          try {
            const response = await axios.post(
              `/api/host/${props.edit ? 'edit' : 'new'}`,
              {
                house: {
                  id: props.edit ? id : null,
                  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'
            value={title}
          />
        </p>
        <p>
          <label>Town</label>
          <input
            required
            onChange={event => setTown(event.target.value)}
            type='text'
            placeholder='Town'
            value={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'
            value={picture}
          />
        </p>
        <p>
          <label>House description</label>
          <textarea
            required
            onChange={event => setDescription(event.target.value)}
            value={description}></textarea>
        </p>

        <div className='grid'>
          <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>

        {props.edit ? <button>Edit house</button> : <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;
        }
        form p {
          display: grid;
        }

        .grid {
          display: grid;
          grid-template-columns: 50% 50%;
        }

        .grid > div {
          padding: 50px;
        }
      `}</style>
    </div>
  )
}

export default HouseForm

现在pages/host/new.js 已经小了很多。

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

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

const NewHouse = () => {
  return (
    <Layout
      content={
        <>
          <Head>
            <title>Add a new house</title>
          </Head>

          <HouseForm edit={false} />
        </>
      }
    />
  )
}

export default NewHouse

这里是我们的pages/host/[id].js

import axios from 'axios'
import Layout from '../../components/Layout'
import HouseForm from '../../components/HouseForm'
import Head from 'next/head'

const EditHouse = props => {
  return (
    <Layout
      content={
        <>
          <Head>
            <title>Edit house</title>
          </Head>

          <HouseForm edit={true} house={props.house} />
        </>
      }
    />
  )
}

EditHouse.getInitialProps = async ({ query }) => {
  const { id } = query
  const response = await axios.get(`http://localhost:3000/api/houses/${id}`)

  return {
    house: response.data
  }
}

export default EditHouse

看到了吗?这里我们传递了house 道具,它包含了房子的数据。

现在我们需要定义一个API,在server.js 中监听POST/api/host/edit

server.js

server.post('/api/host/edit', async (req, res) => {

})

在那里,我们必须在Sequelize House模型上调用update() 函数,但在这之前(在检查用户是否登录后),我们要确保当前用户也是房子的主人,以禁止编辑其他人的房子。

怎么做?我们得到用户的电子邮件,我们要求用户模型找到该用户,我们检查该用户是否是主人,最后我们更新数据。

记得吗?我们已经在Stripe webhook handler中使用了update() 方法。

这就是代码。

server.post('/api/host/edit', 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 => {
    House.findByPk(houseData.id).then(house => {
      if (house) {
        if (house.host !== user.id) {
          res.writeHead(403, {
            'Content-Type': 'application/json'
          })
          res.end(
            JSON.stringify({
              status: 'error',
              message: 'Unauthorized'
            })
          )

          return
        }

        House.update(houseData, {
          where: {
            id: houseData.id
          }
        })
          .then(() => {
            res.writeHead(200, {
              'Content-Type': 'application/json'
            })
            res.end(JSON.stringify({ status: 'success', message: 'ok' }))
          })
          .catch(err => {
            res.writeHead(500, {
              'Content-Type': 'application/json'
            })
            res.end(JSON.stringify({ status: 'error', message: err.name }))
          })
      } else {
        res.writeHead(404, {
          'Content-Type': 'application/json'
        })
        res.end(
          JSON.stringify({
            message: `Not found`
          })
        )
        return
      }
    })
  })
})

其中有很多只是为了处理错误和各种边缘情况。

下一部分。克隆Airbnb,房屋描述的安全HTML