Skip to content

Commit

Permalink
Merge pull request #222 from thebeetoken/jeremy/ENG-997b-current-upco…
Browse files Browse the repository at this point in the history
…ming

Jeremy/eng 997b current upcoming
  • Loading branch information
jerminatorhits authored Mar 4, 2019
2 parents 9dc249a + 1eb140d commit 7f994de
Show file tree
Hide file tree
Showing 14 changed files with 519 additions and 98 deletions.
5 changes: 3 additions & 2 deletions src/components/shared/loading/Loading/Loading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@ import { Spinner } from 'reactstrap';

type Props = Partial<{
color: string; // 'primary'
className?: string;
height: string; // '8rem'
type: string; // 'grow' or 'border'
width: string; // '8rem'
}>;

const Loading = (props: Props) => {
const { color, height, type, width } = props;
const { className, color, height, type, width } = props;
const style = {
height: height || '3rem',
width: width || '3rem',
};

return <Spinner color={color || 'primary'} style={style} type={type || 'grow'} />;
return <Spinner className={`${className || ''}`.trim()} color={color || 'primary'} style={style} type={type || 'grow'} />;
};

export default Loading;
89 changes: 89 additions & 0 deletions src/components/work/CancelBookingModal.tsx/CancelBookingModal.tsx
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);
1 change: 1 addition & 0 deletions src/components/work/CancelBookingModal.tsx/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './CancelBookingModal';
162 changes: 162 additions & 0 deletions src/components/work/ContactHostFormModal/ContactHostFormModal.tsx
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);
1 change: 1 addition & 0 deletions src/components/work/ContactHostFormModal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './ContactHostFormModal';
40 changes: 40 additions & 0 deletions src/components/work/LoadingPortal/LoadingPortal.tsx
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>
);
1 change: 1 addition & 0 deletions src/components/work/LoadingPortal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './LoadingPortal';
Loading

0 comments on commit 7f994de

Please sign in to comment.