15 StayBooking Client 5
Create a component src/components/GuestHomePage.js
import React from "react";
import { Tabs } from "antd";
const { TabPane } = Tabs;
class GuestHomePage extends React.Component {
render() {
return (
<Tabs defaultActiveKey="1" destroyInactiveTabPane={true}>
<TabPane tab="Search Stays" key="1">
Search Stays Content
</TabPane>
<TabPane tab="My Reservations" key="2">
My Reservations Content
</TabPane>
</Tabs>
);
}
}
export default GuestHomePage;
Replace the placeholder in app.js
...
import GuestHomePage from "./components/GuestHomePage";
...
renderContent = () => {
...
return <GuestHomePage />;
};
...
export default App;
Build a component for the “my reservations tab”, in GuestHomePage.js
import React from "react";
import { message, Tabs, List, Typography } from "antd";
import { getReservations } from "../utils";
const { TabPane } = Tabs;
const { Text } = Typography;
class MyReservations extends React.Component {
state = {
loading: false,
data: [],
};
componentDidMount() {
this.loadData();
}
loadData = async () => {
this.setState({
loading: true,
});
try {
const resp = await getReservations();
this.setState({
data: resp,
});
} catch (error) {
message.error(error.message);
} finally {
this.setState({
loading: false,
});
}
};
render() {
return (
<List
style={{ width: 1000, margin: "auto" }}
loading={this.state.loading}
dataSource={this.state.data}
renderItem={(item) => (
<List.Item actions={[]}>
<List.Item.Meta
title={<Text>{item.stay.name}</Text>}
description={
<>
<Text>Checkin Date: {item.checkin_date}</Text>
<br />
<Text>Checkout Date: {item.checkout_date}</Text>
</>
}
/>
</List.Item>
)}
/>
);
}
}
Replace the place holder with the real component for my reservations tab
Build component for the Search Stays tab content (in GuestHomePage.js)
import React from "react";
import {
Image,
message,
Tabs,
List,
Typography,
Form,
InputNumber,
DatePicker,
Button,
Card,
Carousel,
} from "antd";
import { getReservations, searchStays } from "../utils";
import { LeftCircleFilled, RightCircleFilled } from "@ant-design/icons";
import { StayDetailInfoButton } from "./HostHomePage";
...
class SearchStays extends React.Component {
state = {
data: [],
loading: false,
};
search = async (query) => {
this.setState({
loading: true,
});
try {
const resp = await searchStays(query);
this.setState({
data: resp,
});
} catch (error) {
message.error(error.message);
} finally {
this.setState({
loading: false,
});
}
};
render() {
return (
<>
<Form onFinish={this.search} layout="inline">
<Form.Item
label="Guest Number"
name="guest_number"
rules={[{ required: true }]}
>
<InputNumber min={1} />
</Form.Item>
<Form.Item
label="Checkin Date"
name="checkin_date"
rules={[{ required: true }]}
>
<DatePicker />
</Form.Item>
<Form.Item
label="Checkout Date"
name="checkout_date"
rules={[{ required: true }]}
>
<DatePicker />
</Form.Item>
<Form.Item>
<Button
loading={this.state.loading}
type="primary"
htmlType="submit"
>
Submit
</Button>
</Form.Item>
</Form>
<List
style={{ marginTop: 20 }}
loading={this.state.loading}
grid={{
gutter: 16,
xs: 1,
sm: 3,
md: 3,
lg: 3,
xl: 4,
xxl: 4,
}}
dataSource={this.state.data}
renderItem={(item) => (
<List.Item>
<Card
key={item.id}
title={
<div style={{ display: "flex", alignItems: "center" }}>
<Text ellipsis={true} style={{ maxWidth: 150 }}>
{item.name}
</Text>
<StayDetailInfoButton stay={item} />
</div>
}
extra={null}
>
{
<Carousel
dots={false}
arrows={true}
prevArrow={<LeftCircleFilled />}
nextArrow={<RightCircleFilled />}
>
{item.images.map((image, index) => (
<div key={index}>
<Image src={image.url} width="100%" />
</div>
))}
</Carousel>
}
</Card>
</List.Item>
)}
/>
</>
);
}
}
Book stays button (src\components\GuestHomePage.js)
import React from "react";
import {
Image,
message,
Tabs,
List,
Typography,
Form,
InputNumber,
DatePicker,
Button,
Card,
Carousel,
Modal,
} from "antd";
import { bookStay, getReservations, searchStays } from "../utils";
import { LeftCircleFilled, RightCircleFilled } from "@ant-design/icons";
import { StayDetailInfoButton } from "./HostHomePage";
...
class BookStayButton extends React.Component {
state = {
loading: false,
modalVisible: false,
};
handleCancel = () => {
this.setState({
modalVisible: false,
});
};
handleBookStay = () => {
this.setState({
modalVisible: true,
});
};
handleSubmit = async (values) => {
const { stay } = this.props;
this.setState({
loading: true,
});
try {
await bookStay({
checkin_date: values.checkin_date.format("YYYY-MM-DD"),
checkout_date: values.checkout_date.format("YYYY-MM-DD"),
stay: {
id: stay.id,
},
});
message.success("Successfully book stay");
} catch (error) {
message.error(error.message);
} finally {
this.setState({
loading: false,
});
}
};
render() {
const { stay } = this.props;
return (
<>
<Button onClick={this.handleBookStay} shape="round" type="primary">
Book Stay
</Button>
<Modal
destroyOnClose={true}
title={stay.name}
visible={this.state.modalVisible}
footer={null}
onCancel={this.handleCancel}
>
<Form
preserve={false}
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
onFinish={this.handleSubmit}
>
<Form.Item
label="Checkin Date"
name="checkin_date"
rules={[{ required: true }]}
>
<DatePicker />
</Form.Item>
<Form.Item
label="Checkout Date"
name="checkout_date"
rules={[{ required: true }]}
>
<DatePicker />
</Form.Item>
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
<Button
loading={this.state.loading}
type="primary"
htmlType="submit"
>
Book
</Button>
</Form.Item>
</Form>
</Modal>
</>
);
}
}
Replace placeholder with book stay button
class SearchStays extends React.Component {
...
render() {
return (
<>
...
<Card
...
extra={<BookStayButton stay={item} />}
>
...
</>
);
}
}
Cancel Reservations Button (src\components\GuestHomePage.js)
import React from "react";
import {
Image,
message,
Tabs,
List,
Typography,
Form,
InputNumber,
DatePicker,
Button,
Card,
Carousel,
Modal,
} from "antd";
import { bookStay, cancelReservation, getReservations, searchStays } from "../utils";
import { LeftCircleFilled, RightCircleFilled } from "@ant-design/icons";
import { StayDetailInfoButton } from "./HostHomePage";
...
class CancelReservationButton extends React.Component {
state = {
loading: false,
};
handleCancelReservation = async () => {
const { reservationId, onCancelSuccess } = this.props;
this.setState({
loading: true,
});
try {
await cancelReservation(reservationId);
} catch (error) {
message.error(error.message);
} finally {
this.setState({
loading: false,
});
}
onCancelSuccess();
};
render() {
return (
<Button
loading={this.state.loading}
onClick={this.handleCancelReservation}
danger={true}
shape="round"
type="primary"
>
Cancel Reservation
</Button>
);
}
}
Replace the place holder for cancel reservation button
class MyReservations extends React.Component {
...
renderItem={(item) => {
<List.Item
actions={[
<CancelReservationButton onCancelSuccess={this.loadData} reservationId={item.id} />,
]}
>
...
}
Replace the place holder for search stays (src\components\GuestHomePage.js, in component “GuestHomePage”)
...
<Tabs defaultActiveKey="1" destroyInactiveTabPane={true}>
<TabPane tab="Search Stays" key="1">
<SearchStays />
</TabPane>
…
Google could 尽量选稍微的贵的, 不然服务器总是重启
因为用了prodxy
这边domain需要改下