-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #222 from thebeetoken/jeremy/ENG-997b-current-upco…
…ming Jeremy/eng 997b current upcoming
- Loading branch information
Showing
14 changed files
with
519 additions
and
98 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
89 changes: 89 additions & 0 deletions
89
src/components/work/CancelBookingModal.tsx/CancelBookingModal.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import * as React from 'react'; | ||
import { ModalHeader, Modal, ModalBody, ModalFooter, Button } from 'reactstrap'; | ||
import { GUEST_CANCEL_BOOKING, Booking, Currency, GET_GUEST_SORTED_BOOKINGS } from 'networking/bookings'; | ||
import { graphql, compose } from 'react-apollo'; | ||
import { differenceInDays } from 'date-fns'; | ||
import { cancel, loadWeb3 } from 'utils/web3'; | ||
import { AlertProperties } from 'components/work/Alert/Alert'; | ||
import LoadingPortal from 'components/work/LoadingPortal'; | ||
import { getFriendlyErrorMessage } from 'utils/validators'; | ||
|
||
interface Props { | ||
booking: Booking; | ||
cancelBooking: (booking: Booking) => Promise<Booking>; | ||
onModalAction: () => void; | ||
setAlert: (alert: AlertProperties) => void; | ||
} | ||
|
||
function CancelBookingModal({ booking, cancelBooking, onModalAction, setAlert }: Props) { | ||
const [isSubmitting, setSubmitting] = React.useState<boolean>(false); | ||
|
||
if (isSubmitting) { | ||
return <LoadingPortal currency={booking.currency} />; | ||
} | ||
|
||
return ( | ||
<Modal isOpen toggle={() => !isSubmitting && onModalAction()}> | ||
<ModalHeader>Cancel Booking</ModalHeader> | ||
<ModalBody> | ||
<h6>Are you sure you want to cancel this booking?</h6> | ||
<h6>Booking: {booking.id}</h6> | ||
</ModalBody> | ||
<ModalFooter> | ||
<Button color="secondary" disabled={isSubmitting} onClick={onModalAction}>Back</Button>{' '} | ||
<Button color="danger" disabled={isSubmitting} onClick={handleCancelBooking}>Yes, Cancel Booking</Button> | ||
</ModalFooter> | ||
</Modal> | ||
); | ||
|
||
function handleCancelBooking() { | ||
setSubmitting(true); | ||
cancelBooking(booking) | ||
.then(() => { | ||
setAlert({ | ||
color: 'success', | ||
msg: 'Your booking has been cancelled', | ||
show: true, | ||
}); | ||
}) | ||
.catch((error: Error) => { | ||
setAlert({ | ||
color: 'danger', | ||
msg: `There was an error processing your request. ${getFriendlyErrorMessage(error)}`, | ||
show: true, | ||
}); | ||
}) | ||
.finally(() => { | ||
setSubmitting(false) | ||
onModalAction(); | ||
}); | ||
}; | ||
} | ||
|
||
export default compose( | ||
graphql(GUEST_CANCEL_BOOKING, { | ||
props: ({ mutate }: any) => ({ | ||
cancelBooking: async (booking: Booking) => { | ||
const { id, currency, checkInDate, status } = booking; | ||
const days = differenceInDays(checkInDate, Date.now()); | ||
if (currency === Currency.BEE && days >= 7 && status === 'guest_paid') { | ||
const web3 = loadWeb3(); | ||
await cancel(web3.eth, id); | ||
} | ||
return mutate({ | ||
variables: { id }, | ||
refetchQueries: [{ query: GET_GUEST_SORTED_BOOKINGS }], | ||
update: (store: any, { data: guestCancelBooking }: any) => { | ||
if (!store.data.data.ROOT_QUERY || !store.data.data.ROOT_QUERY.allBookings) { | ||
return; | ||
} | ||
const { allBookings } = store.readQuery({ query: GET_GUEST_SORTED_BOOKINGS }); | ||
const index = allBookings.findIndex((booking: Booking) => booking.id === id); | ||
allBookings[index].status = guestCancelBooking.status; | ||
store.writeQuery({ query: GET_GUEST_SORTED_BOOKINGS, data: allBookings }); | ||
}, | ||
}); | ||
}, | ||
}), | ||
}) | ||
)(CancelBookingModal); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default } from './CancelBookingModal'; |
162 changes: 162 additions & 0 deletions
162
src/components/work/ContactHostFormModal/ContactHostFormModal.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
import * as React from 'react'; | ||
import { Formik, Field, Form as FormikForm } from 'formik'; | ||
import * as Yup from 'yup'; | ||
import { compose, graphql } from 'react-apollo'; | ||
|
||
import { Booking } from 'networking/bookings'; | ||
import { CONTACT_USER, ContactUserField, User } from 'networking/users'; | ||
import { Button, Form, FormGroup, Label, FormFeedback, Input, ModalFooter, ModalBody, Modal, ModalHeader } from 'reactstrap'; | ||
import Textarea from 'components/shared/Textarea'; | ||
import { TextareaEvent } from 'components/shared/Textarea/Textarea'; | ||
import Loading from 'components/shared/loading/Loading'; | ||
|
||
interface Props { | ||
contactUser: (input: ContactUserInput) => Promise<EmailResponse>; | ||
booking: Booking; | ||
onModalAction: () => void; | ||
} | ||
|
||
interface FormValues { | ||
[name: string]: string; | ||
} | ||
|
||
interface ContactUserInput { | ||
bookingId?: string; | ||
listingId?: string; | ||
message: string; | ||
recipientId: string; | ||
subject: string; | ||
} | ||
|
||
interface EmailResponse { | ||
bookingId: string; | ||
listingId: string; | ||
message: string; | ||
recipient: User; | ||
subject: string; | ||
} | ||
|
||
const defaultValues: FormValues = { | ||
[ContactUserField.SUBJECT]: '', | ||
[ContactUserField.MESSAGE]: '', | ||
}; | ||
|
||
const ContactHostSchema = Yup.object({ | ||
subject: Yup.string().required('Please fill out the subject field.'), | ||
message: Yup.string().required('Please fill out the message field.'), | ||
}); | ||
|
||
function ContactHostForm({ booking, contactUser, onModalAction }: Props) { | ||
const [successMessage, setSuccessMessage] = React.useState<string>(''); | ||
const [errorMessage, setErrorMessage] = React.useState<string>(''); | ||
const { host, listingId, id } = booking; | ||
|
||
if (successMessage) { | ||
return ( | ||
<Modal isOpen toggle={onModalAction}> | ||
<ModalHeader>Message Successfully Sent</ModalHeader> | ||
<ModalBody> | ||
<p>{successMessage}</p> | ||
</ModalBody> | ||
<ModalFooter> | ||
<Button color="success" onClick={onModalAction}> | ||
Okay | ||
</Button> | ||
</ModalFooter> | ||
</Modal> | ||
); | ||
} | ||
return ( | ||
<Formik | ||
initialValues={defaultValues} | ||
isInitialValid | ||
validationSchema={ContactHostSchema} | ||
onSubmit={({ message, subject }, actions) => { | ||
const input = { | ||
bookingId: id, | ||
listingId, | ||
message, | ||
recipientId: host.id, | ||
subject, | ||
}; | ||
return contactUser(input) | ||
.then((response: any) => { | ||
const emailResponse: EmailResponse = response.data.contactUser; | ||
const { subject, recipient } = emailResponse || { subject: '', recipient: { firstName: 'the host' } }; | ||
const success = `Your message ${subject ? `"${subject}" ` : ' '}was sent to ${recipient.firstName}.`; | ||
setSuccessMessage(success); | ||
}) | ||
.catch((error: Error) => { | ||
console.error(error); | ||
setErrorMessage(`${error.message}. If this continues to occur, please contact us at support@beenest.com`); | ||
}) | ||
.finally(() => actions.setSubmitting(false)); | ||
}} | ||
> | ||
{({ errors, isSubmitting, setFieldTouched, setFieldValue, touched, values }) => ( | ||
<Modal isOpen toggle={onModalAction}> | ||
<Form tag={FormikForm}> | ||
<ModalHeader>Contact {host.firstName || 'Host'}</ModalHeader> | ||
<ModalBody> | ||
<FormGroup> | ||
<Label for={ContactUserField.SUBJECT}>Subject</Label> | ||
<Input | ||
id={ContactUserField.SUBJECT} | ||
invalid={!!errors.subject && !!touched.subject} | ||
name={ContactUserField.SUBJECT} | ||
placeholder="First name" | ||
tag={Field} | ||
type="text" | ||
/> | ||
<FormFeedback>{errors.subject}</FormFeedback> | ||
</FormGroup> | ||
|
||
<FormGroup> | ||
<Label for={ContactUserField.MESSAGE}>Message</Label> | ||
<Textarea | ||
className={`form-control${errors.message && touched.message ? ' is-invalid' : ''}`} | ||
html | ||
name={ContactUserField.MESSAGE} | ||
onBlur={() => setFieldTouched(ContactUserField.MESSAGE, true)} | ||
onChange={(event: TextareaEvent) => { | ||
setFieldValue(ContactUserField.MESSAGE, event.target.value); | ||
}} | ||
placeholder="Type in your message here" | ||
textareaHeight="164px" | ||
value={values.message} | ||
/> | ||
<FormFeedback>{errors.message}</FormFeedback> | ||
</FormGroup> | ||
</ModalBody> | ||
|
||
<ModalFooter> | ||
{errorMessage && <FormFeedback className="d-block">{errorMessage.slice(0, 150)}</FormFeedback>} | ||
<Button | ||
color="secondary" | ||
className="d-flex align-items-center justify-content-center" | ||
disabled={isSubmitting} | ||
style={{ width: '180px' }} // TODO: Make button full width for mobile | ||
type="submit"> | ||
{isSubmitting | ||
? <Loading height="1.5rem" width="1.5rem" /> | ||
: 'Send Message'} | ||
</Button> | ||
</ModalFooter> | ||
</Form> | ||
</Modal> | ||
)} | ||
</Formik> | ||
); | ||
} | ||
|
||
export default compose( | ||
graphql(CONTACT_USER, { | ||
props: ({ mutate }: any) => ({ | ||
contactUser: (input: ContactUserInput) => { | ||
return mutate({ | ||
variables: { input }, | ||
}); | ||
}, | ||
}), | ||
}) | ||
)(ContactHostForm); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default } from './ContactHostFormModal'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import * as React from 'react'; | ||
|
||
import { Currency } from 'networking/bookings'; | ||
import Loading from 'components/shared/loading/Loading'; | ||
import { Container, Modal } from 'reactstrap'; | ||
import { VIEWPORT_CENTER_LAYOUT } from 'styled/sharedClasses/layout'; | ||
|
||
interface Props { | ||
currency?: Currency | null; | ||
message?: string; | ||
} | ||
|
||
const SUPPORT_EMAIL = 'support@beenest.com'; | ||
const ContactSupport = (): JSX.Element => <p>If you have any issues, please contact support at {SUPPORT_EMAIL}.</p>; | ||
|
||
export default ({ currency, message }: Props) => ( | ||
<Modal isOpen className={VIEWPORT_CENTER_LAYOUT}> | ||
<Container className="text-center p-6"> | ||
<Loading className="mb-4" height="6rem" width="6rem" /> | ||
{currency === (Currency.USD || Currency.BTC) | ||
? | ||
<> | ||
<h2>Processing request...</h2> | ||
{!!message && <p className="mb-0">{message}</p>} | ||
<p className="mb-0">Please wait while we process your request.</p> | ||
<p className="mb-0">This may take up to 30 seconds to complete.</p> | ||
<ContactSupport /> | ||
</> | ||
: | ||
<> | ||
<h2>Processing transaction...</h2> | ||
{!!message && <p className="mb-0">{message}</p>} | ||
<p className="mb-0">Please Confirm this transaction with your wallet (e.g. MetaMask).</p> | ||
<p className="mb-0">Crypto transactions may take up to 30 seconds to complete.</p> | ||
<ContactSupport /> | ||
</> | ||
} | ||
</Container> | ||
</Modal> | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default } from './LoadingPortal'; |
Oops, something went wrong.