这篇文章是一个新系列的一部分,我们用Next.js建立一个克隆的Airbnb。请看第一篇文章。
- 第一部分。让我们从安装Next.js开始
- 第2部分:建立房屋列表
- 第3部分:建立房屋详细视图
- 第4部分:CSS和导航栏
- 第5部分:从日期选择器开始
- 第6部分:添加侧边栏
- 第7部分:添加 react-day-picker
- 第8部分。在页面中添加日历
- 第9部分:配置DayPickerInput组件
- 第10部分:同步开始和结束日期
- 第11部分:显示所选日期的价格
- 第12部分:登录和注册表格
- 第13部分:激活模态
- 第14部分:发送注册数据到服务器
- 第15部分:添加postgres
- 第16部分:实现模型和DB连接
- 第17部分:创建一个会话令牌
- 第18部分:实现登录
- 第19部分:确定我们是否已经登录了
- 第20部分:在我们登录后改变状态
- 第21部分:注册后登录
- 第22部分:创建模型并将数据移到数据库中
- 第23部分:使用数据库而不是文件
- 第24部分:处理预订
- 第25部分:处理预订的日期
- 第26部分:如果已经预订了,则防止预订
- 第27部分:添加Stripe进行支付
- 第28部分:克隆Airbnb,处理Stripe webhooks
- 第29部分:克隆的Airbnb,查看预订情况
- 第30部分:克隆的Airbnb,清理预订
- 第31部分:克隆的Airbnb,管理房屋
- 第32部分:克隆的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