这篇文章是一个新系列的一部分,我们用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,管理房屋
在这一课中,我想添加添加一个新房子的功能。
在上一课中,我添加了一个链接到/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,编辑房屋