@xola/ui-kit 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/.github/ISSUE_TEMPLATE/v2-component.md +12 -0
  2. package/.nvmrc +1 -0
  3. package/.prettierrc +6 -0
  4. package/.storybook/main.js +17 -0
  5. package/.storybook/preview.js +3 -0
  6. package/README.md +1 -0
  7. package/babel.config.json +13 -0
  8. package/build/favicon.ico +0 -0
  9. package/build/style.css +1 -0
  10. package/build/ui-kit.mjs +12455 -0
  11. package/build/ui-kit.umd.js +33 -0
  12. package/lib/index.js +1 -0
  13. package/package.json +48 -0
  14. package/src/components/CountrySelect/CountrySelect.js +15 -0
  15. package/src/components/CountrySelect/index.js +1 -0
  16. package/src/components/DatePicker/DatePicker.js +80 -0
  17. package/src/components/DatePicker/DatePicker.module.scss +3 -0
  18. package/src/components/DatePicker/DatePicker.scss +18 -0
  19. package/src/components/DatePicker/index.js +3 -0
  20. package/src/components/FormInput/FormInput.js +23 -0
  21. package/src/components/FormInput/index.js +3 -0
  22. package/src/components/MiniStepper/MiniStepper.js +39 -0
  23. package/src/components/MiniStepper/MiniStepper.module.scss +9 -0
  24. package/src/components/MiniStepper/index.js +1 -0
  25. package/src/components/PhoneInput/PhoneInput.js +44 -0
  26. package/src/components/PhoneInput/index.js +1 -0
  27. package/src/components/ProductSelector/ProductListItem/ProductListItem.js +41 -0
  28. package/src/components/ProductSelector/ProductListItem/ProductListItem.module.scss +32 -0
  29. package/src/components/ProductSelector/ProductSelector.js +71 -0
  30. package/src/components/ProductSelector/index.js +3 -0
  31. package/src/components/ScheduleEditor/ScheduleEditor.js +277 -0
  32. package/src/components/ScheduleEditor/ScheduleEditorRow.js +26 -0
  33. package/src/components/ScheduleEditor/TimeRangeSelector/TimeRangeSelector.js +94 -0
  34. package/src/components/ScheduleEditor/TimeRangeSelector/TimeRangeSelector.module.scss +13 -0
  35. package/src/components/ScheduleEditor/TimeSlotSelector/TimeSlotSelector.js +62 -0
  36. package/src/components/ScheduleEditor/TimeSlotSelector/TimeSlotSelector.module.scss +21 -0
  37. package/src/components/ScheduleEditor/WeekSelector/WeekSelector.js +50 -0
  38. package/src/components/ScheduleEditor/helpers/schedule.js +15 -0
  39. package/src/components/ScheduleEditor/helpers/scheduleSummary.js +128 -0
  40. package/src/components/ScheduleEditor/index.js +4 -0
  41. package/src/components/Stepper/Step.js +31 -0
  42. package/src/components/Stepper/Step.module.scss +25 -0
  43. package/src/components/Stepper/Stepper.js +25 -0
  44. package/src/components/Stepper/Stepper.module.scss +5 -0
  45. package/src/components/Stepper/index.js +4 -0
  46. package/src/components/TimePicker/TimePicker.js +51 -0
  47. package/src/components/TimePicker/TimePicker.module.scss +16 -0
  48. package/src/components/TimePicker/TimePickerPopover.js +94 -0
  49. package/src/components/TimePicker/TimePickerPopover.module.scss +48 -0
  50. package/src/components/TimePicker/index.js +3 -0
  51. package/src/icons/CheckIcon.js +9 -0
  52. package/src/icons/TrashIcon.js +17 -0
  53. package/src/index.js +18 -0
  54. package/src/stories/Button.stories.js +66 -0
  55. package/src/stories/CountrySelect.stories.js +18 -0
  56. package/src/stories/DatePicker.stories.js +96 -0
  57. package/src/stories/FormInput.stories.js +25 -0
  58. package/src/stories/Forms.stories.js +43 -0
  59. package/src/stories/Icons.stories.js +21 -0
  60. package/src/stories/MiniStepper.stories.js +15 -0
  61. package/src/stories/PhoneInput.stories.js +26 -0
  62. package/src/stories/ProductSelector.stories.js +42 -0
  63. package/src/stories/ScheduleEditor.stories.js +37 -0
  64. package/src/stories/Stepper.stories.js +36 -0
  65. package/src/stories/TimePicker.stories.js +14 -0
  66. package/src/stories/Typography.stories.js +18 -0
  67. package/src/styles/custom.scss +60 -0
  68. package/src/styles/index.scss +3 -0
  69. package/src/styles/variables.scss +43 -0
  70. package/src/theme.js +359 -0
  71. package/src/values/countries.js +196 -0
  72. package/src/values/products.js +86 -0
  73. package/src/values/sellers.js +5 -0
  74. package/webpack.config.js +47 -0
@@ -0,0 +1,80 @@
1
+ import React from "react";
2
+ import { Fragment } from "react";
3
+ import DayPicker, { DateUtils } from "react-day-picker";
4
+ import DayPickerInput from "react-day-picker/DayPickerInput";
5
+ import dateFnsFormat from "date-fns/format";
6
+ import _ from "lodash";
7
+ import classNames from "classnames";
8
+ import "react-day-picker/lib/style.css";
9
+ import "./DatePicker.scss"; // Not using CSS modules here since we need to override styles from react-day-picker.
10
+ import styles from "./DatePicker.module.scss";
11
+
12
+ export const formatDate = (date, format, locale) => {
13
+ return dateFnsFormat(date, format, { locale });
14
+ };
15
+
16
+ const DatePicker = ({
17
+ isDatePicker,
18
+ isMultiple = false,
19
+ value,
20
+ minDate,
21
+ maxDate,
22
+ placeholder,
23
+ onChange,
24
+ format = "MM-dd-yyyy",
25
+ }) => {
26
+ const disableDays = {};
27
+ const dayPickerProps = {};
28
+ if (minDate) {
29
+ disableDays["before"] = minDate;
30
+ }
31
+ if (maxDate) {
32
+ disableDays["after"] = maxDate;
33
+ }
34
+ dayPickerProps["disabledDays"] = disableDays;
35
+
36
+ if (_.isArray(value)) {
37
+ isMultiple = true;
38
+ }
39
+
40
+ if (isDatePicker) {
41
+ dayPickerProps["selectedDays"] = value;
42
+ }
43
+
44
+ const handleChange = (v) => {
45
+ if (isMultiple) {
46
+ if (value) {
47
+ if (_.some(value, (item) => DateUtils.isSameDay(item, v))) {
48
+ _.remove(value, (item) => DateUtils.isSameDay(item, v));
49
+ } else {
50
+ value.push(v);
51
+ }
52
+ } else {
53
+ value = [v];
54
+ }
55
+ onChange(value);
56
+ } else {
57
+ onChange(v);
58
+ }
59
+ };
60
+
61
+ return (
62
+ <Fragment>
63
+ {isDatePicker ? (
64
+ <DayPickerInput
65
+ component={(props) => <input className={classNames(styles.input, "form-control")} {...props} />}
66
+ value={value}
67
+ placeholder={placeholder}
68
+ format={format}
69
+ formatDate={formatDate}
70
+ onDayChange={(v) => handleChange(v)}
71
+ dayPickerProps={dayPickerProps}
72
+ />
73
+ ) : (
74
+ <DayPicker selectedDays={value} onDayClick={(v) => handleChange(v)} dayPickerProps={dayPickerProps} />
75
+ )}
76
+ </Fragment>
77
+ );
78
+ };
79
+
80
+ export default DatePicker;
@@ -0,0 +1,3 @@
1
+ .input {
2
+ width: 150px;
3
+ }
@@ -0,0 +1,18 @@
1
+ @import "../../styles/variables.scss";
2
+
3
+ .DayPicker-Day {
4
+ line-height: normal !important;
5
+ }
6
+
7
+ .DayPicker-Day--selected {
8
+ background-color: theme-color("primary") !important;
9
+ }
10
+
11
+ .DayPicker-Caption,
12
+ .DayPicker-Day:not(.DayPicker-Day--disabled) {
13
+ color: $body-color;
14
+ }
15
+
16
+ .DayPicker-Day--today:not(.DayPicker-Day--selected):not(.DayPicker-Day--disabled) {
17
+ color: theme-color("danger");
18
+ }
@@ -0,0 +1,3 @@
1
+ import DatePicker, { formatDate } from "./DatePicker";
2
+
3
+ export { DatePicker, formatDate };
@@ -0,0 +1,23 @@
1
+ import classNames from "classnames";
2
+ import React, { cloneElement } from "react";
3
+ import { FormGroup, Input, Label } from "reactstrap";
4
+
5
+ const FormInput = ({ label, error, children, ...rest }) => {
6
+ return (
7
+ <FormGroup>
8
+ <Label htmlFor={rest.id} className={classNames({ "text-danger": error })}>
9
+ <span className={classNames({ "font-weight-bold": error })}>{label}</span> {error}
10
+ </Label>
11
+
12
+ {rest.type === "select" || !children ? (
13
+ <Input id={rest.id} invalid={!!error} {...rest}>
14
+ {children}
15
+ </Input>
16
+ ) : (
17
+ cloneElement(children, rest)
18
+ )}
19
+ </FormGroup>
20
+ );
21
+ };
22
+
23
+ export default FormInput;
@@ -0,0 +1,3 @@
1
+ import FormInput from "./FormInput";
2
+
3
+ export { FormInput };
@@ -0,0 +1,39 @@
1
+ import classNames from "classnames";
2
+ import PropTypes from "prop-types";
3
+ import React from "react";
4
+ import styles from "./MiniStepper.module.scss";
5
+
6
+ export const MiniStepper = ({ count, value, size, ...rest }) => {
7
+ const dots = [];
8
+
9
+ for (let i = 0; i < count; i++) {
10
+ dots.push(
11
+ <span
12
+ style={{ fontSize: size }}
13
+ key={i}
14
+ className={classNames(styles.dot, { [styles.done]: i < value }, "mr-1")}
15
+ >
16
+ &bull;
17
+ </span>,
18
+ );
19
+ }
20
+
21
+ return <div {...rest}>{dots}</div>;
22
+ };
23
+
24
+ MiniStepper.propTypes = {
25
+ /**
26
+ * Total number of steps
27
+ */
28
+ count: PropTypes.number.isRequired,
29
+
30
+ /**
31
+ * Current step index
32
+ */
33
+ value: PropTypes.number.isRequired,
34
+
35
+ /**
36
+ * Size (font size) of each dot
37
+ */
38
+ size: PropTypes.number,
39
+ };
@@ -0,0 +1,9 @@
1
+ @import "../../styles/variables.scss";
2
+
3
+ .dot {
4
+ color: $gray-300;
5
+ }
6
+
7
+ .done {
8
+ color: $gray-900;
9
+ }
@@ -0,0 +1 @@
1
+ export * from "./MiniStepper";
@@ -0,0 +1,44 @@
1
+ import { AsYouType, getCountryCallingCode } from "libphonenumber-js";
2
+ import PropTypes from "prop-types";
3
+ import React, { useRef, useState } from "react";
4
+ import { Input, InputGroup } from "reactstrap";
5
+ import { CountrySelect } from "../CountrySelect";
6
+
7
+ const getCountry = (value) => {
8
+ const asYouType = new AsYouType();
9
+ asYouType.input(value);
10
+ const phoneNumber = asYouType.getNumber();
11
+ return phoneNumber && phoneNumber.country ? phoneNumber.country : null;
12
+ };
13
+
14
+ export const PhoneInput = ({ value, onChange, error, defaultCountry = "", ...rest }) => {
15
+ const [country, setCountry] = useState(defaultCountry);
16
+ const inputRef = useRef();
17
+ const invalid = !!error;
18
+
19
+ const handleValueChange = (e) => {
20
+ onChange(e.target.value);
21
+ setCountry(getCountry(value) || country);
22
+ };
23
+
24
+ const handleCountryChange = (e) => {
25
+ const country = e.target.value;
26
+ setCountry(country);
27
+ const value = "+" + getCountryCallingCode(country);
28
+ onChange(value);
29
+ inputRef.current.focus();
30
+ };
31
+
32
+ return (
33
+ <InputGroup>
34
+ <CountrySelect invalid={invalid} style={{ maxWidth: 60 }} value={country} onChange={handleCountryChange} />
35
+ <Input invalid={invalid} {...rest} innerRef={inputRef} onChange={handleValueChange} value={value} />
36
+ </InputGroup>
37
+ );
38
+ };
39
+
40
+ PhoneInput.propTypes = {
41
+ onChange: PropTypes.func,
42
+ value: PropTypes.any,
43
+ defaultCountry: PropTypes.string,
44
+ };
@@ -0,0 +1 @@
1
+ export * from "./PhoneInput";
@@ -0,0 +1,41 @@
1
+ import classNames from "classnames";
2
+ import React from "react";
3
+ import { Col, Row } from "reactstrap";
4
+ import { CheckIcon } from "../../../";
5
+ import styles from "./ProductListItem.module.scss";
6
+
7
+ const ProductListItem = ({ product, isSelected, onProductSelect }) => {
8
+ return (
9
+ <Col
10
+ xs={12}
11
+ md={6}
12
+ className={classNames(styles.item, "cursor-pointer px-0 my-2")}
13
+ onClick={() => onProductSelect(product.id)}
14
+ >
15
+ <Row className={classNames({ [styles.selected]: isSelected }, styles.row, "mx-3 p-3")}>
16
+ <Col xs={3} md={2} className={classNames(styles.thumbnailContainer, "p-0")}>
17
+ <div className={classNames(styles.thumbnail, "w-100 m-0")}>
18
+ {product.photo?.src ? (
19
+ <img className="mw-100" alt="experience" src={product.photo.src} />
20
+ ) : null}
21
+ {isSelected ? (
22
+ <div
23
+ className={classNames(
24
+ styles.selected,
25
+ "w-100 h-100 text-white d-flex text-center align-items-center justify-content-center",
26
+ )}
27
+ >
28
+ <CheckIcon style={{ width: 28, height: 28 }} />
29
+ </div>
30
+ ) : null}
31
+ </div>
32
+ </Col>
33
+ <Col xs={9} md={10}>
34
+ <span className={styles.title}>{product.name}</span>
35
+ </Col>
36
+ </Row>
37
+ </Col>
38
+ );
39
+ };
40
+
41
+ export default ProductListItem;
@@ -0,0 +1,32 @@
1
+ @import "../../../styles/variables.scss";
2
+
3
+ .title {
4
+ font-size: 16px;
5
+ }
6
+
7
+ .item {
8
+ .row {
9
+ opacity: 0.6;
10
+ }
11
+ .selected {
12
+ opacity: 1;
13
+ }
14
+ &:hover {
15
+ .row {
16
+ background-color: $hover-background;
17
+ opacity: 1;
18
+ }
19
+ }
20
+ }
21
+
22
+ .thumbnailContainer {
23
+ min-height: 30px;
24
+ height: fit-content;
25
+ }
26
+
27
+ .thumbnail .selected {
28
+ position: absolute;
29
+ font-size: 24px;
30
+ background-color: rgba($color: theme-color("success"), $alpha: 0.7);
31
+ top: 0;
32
+ }
@@ -0,0 +1,71 @@
1
+ import React, { useState } from "react";
2
+ import classNames from "classnames";
3
+ import _ from "lodash";
4
+ import { Container, Row, Col, Label, Input } from "reactstrap";
5
+ import ProductListItem from "./ProductListItem/ProductListItem";
6
+
7
+ const filterProductsBySearch = (searchQuery = "", products = []) => {
8
+ return products.filter((product) => {
9
+ return product.name.toLowerCase().includes(searchQuery.trim().toLowerCase());
10
+ });
11
+ };
12
+
13
+ const ProductSelector = ({ products = [], value = [], onChange }) => {
14
+ const [searchQuery, setSearchQuery] = useState("");
15
+ const filteredProducts = filterProductsBySearch(searchQuery, products);
16
+
17
+ const isProductSelected = (productId) => {
18
+ return value.some((selectedProduct) => selectedProduct.id === productId);
19
+ };
20
+
21
+ const onProductSelect = (productId) => {
22
+ if (isProductSelected(productId)) {
23
+ onChange(value.filter((v) => productId !== v.id));
24
+ } else {
25
+ onChange([...value, products.find((p) => p.id === productId)]);
26
+ }
27
+ };
28
+
29
+ const onSelectAll = (e) => {
30
+ onChange(e.target.checked ? products : []);
31
+ };
32
+
33
+ return (
34
+ <Container className={classNames("p-0")}>
35
+ <Row className={classNames("d-flex align-items-center bg-light mb-1 p-3")}>
36
+ <Col md={8}>
37
+ <Label check>
38
+ <Input
39
+ type="checkbox"
40
+ checked={products.length === value.length}
41
+ onChange={(e) => onSelectAll(e)}
42
+ />{" "}
43
+ {value.length} Listing(s) selected updated
44
+ </Label>
45
+ </Col>
46
+ <Col md={4}>
47
+ <Input
48
+ type="text"
49
+ onChange={(e) => setSearchQuery(e.target.value)}
50
+ placeholder="Search"
51
+ className="float-right"
52
+ />
53
+ </Col>
54
+ </Row>
55
+ <Row>
56
+ {filteredProducts.map((product) => {
57
+ return (
58
+ <ProductListItem
59
+ key={product.id}
60
+ onProductSelect={onProductSelect}
61
+ isSelected={isProductSelected(product.id)}
62
+ product={product}
63
+ />
64
+ );
65
+ })}
66
+ </Row>
67
+ </Container>
68
+ );
69
+ };
70
+
71
+ export default ProductSelector;
@@ -0,0 +1,3 @@
1
+ import ProductSelector from "./ProductSelector";
2
+
3
+ export { ProductSelector };
@@ -0,0 +1,277 @@
1
+ import React, { Fragment } from "react";
2
+ import { CustomInput, Input, InputGroup, InputGroupAddon, InputGroupText } from "reactstrap";
3
+ import _ from "lodash";
4
+ import classNames from "classnames";
5
+ import ScheduleEditorRow from "./ScheduleEditorRow";
6
+ import { DatePicker } from "../..";
7
+ import WeekSelector from "./WeekSelector/WeekSelector";
8
+ import TimeSlotSelector from "./TimeSlotSelector/TimeSlotSelector";
9
+ import TimeRangeSelector from "./TimeRangeSelector/TimeRangeSelector";
10
+ import { getScheduleSummary } from "./helpers/scheduleSummary";
11
+ import { getScheduleDefaultValues } from "./helpers/schedule";
12
+
13
+ const ScheduleEditor = ({ value = {}, errors = {}, price = 0, isNew = true, onChange }) => {
14
+ value = JSON.parse(JSON.stringify(value));
15
+ if (value.dates?.length > 0) {
16
+ value.dates = value.dates.map((date) => new Date(date));
17
+ }
18
+ if (value.start) {
19
+ value.start = new Date(value.start);
20
+ }
21
+ if (value.end) {
22
+ value.end = new Date(value.end);
23
+ }
24
+ const today = new Date();
25
+ const defaultScheduleEditorValues = getScheduleDefaultValues();
26
+ const schedule = _.extend(defaultScheduleEditorValues, value);
27
+
28
+ const handleChange = (value, key) => {
29
+ let updatedSchedule = { ...schedule };
30
+ updatedSchedule[key] = value;
31
+ onChange({ ...updatedSchedule, summary: getScheduleSummary(updatedSchedule) });
32
+ };
33
+
34
+ const handleAllowedPrivaciesChange = (privacy) => {
35
+ let updatedPrivacies = [...schedule.allowedPrivacies];
36
+ if (updatedPrivacies.includes(privacy)) {
37
+ updatedPrivacies = updatedPrivacies.filter((p) => p !== privacy);
38
+ } else {
39
+ updatedPrivacies.push(privacy);
40
+ }
41
+ handleChange(updatedPrivacies, "allowedPrivacies");
42
+ };
43
+
44
+ return (
45
+ <div>
46
+ <ScheduleEditorRow label="Name" htmlFor="scheduleName">
47
+ <Input
48
+ value={schedule.name}
49
+ onChange={(e) => handleChange(e.target.value, "name")}
50
+ className="w-50"
51
+ type="name"
52
+ name="scheduleName"
53
+ id="scheduleName"
54
+ placeholder="Daily"
55
+ />
56
+ </ScheduleEditorRow>
57
+ <ScheduleEditorRow
58
+ label="Allowed Privacies"
59
+ htmlFor="allowedPrivacies"
60
+ error={errors && errors.allowedPrivacies}
61
+ >
62
+ <div>
63
+ <CustomInput
64
+ inline
65
+ type="checkbox"
66
+ name="allowedPrivacies"
67
+ id="privacy-public"
68
+ label="Public"
69
+ value="public"
70
+ checked={schedule.allowedPrivacies.includes("public")}
71
+ onChange={(e) => handleAllowedPrivaciesChange("public")}
72
+ />
73
+ <CustomInput
74
+ inline
75
+ type="checkbox"
76
+ name="allowedPrivacies"
77
+ id="privacy-private"
78
+ label="Private"
79
+ value="private"
80
+ checked={schedule.allowedPrivacies.includes("private")}
81
+ onChange={(e) => handleAllowedPrivaciesChange("private")}
82
+ />
83
+ </div>
84
+ </ScheduleEditorRow>
85
+ <ScheduleEditorRow label="Availability">
86
+ {isNew ? (
87
+ <div>
88
+ <CustomInput
89
+ inline
90
+ type="radio"
91
+ name="type"
92
+ id="type-available"
93
+ label="Open"
94
+ value="available"
95
+ checked={schedule.type === "available"}
96
+ onChange={(e) => handleChange(e.target.value, "type")}
97
+ />
98
+ <CustomInput
99
+ inline
100
+ type="radio"
101
+ name="type"
102
+ id="type-unavailable"
103
+ label="Blackout"
104
+ value="unavailable"
105
+ checked={schedule.type === "unavailable"}
106
+ onChange={(e) => handleChange(e.target.value, "type")}
107
+ />
108
+ </div>
109
+ ) : (
110
+ <div className="pl-1">{schedule.type === "available" ? "Open" : "Blackout"}</div>
111
+ )}
112
+ </ScheduleEditorRow>
113
+ <ScheduleEditorRow label="Repeats">
114
+ {isNew ? (
115
+ <div>
116
+ <CustomInput
117
+ inline
118
+ type="radio"
119
+ name="repeat"
120
+ id="repeat-weekly"
121
+ label="Weekly"
122
+ value="weekly"
123
+ checked={schedule.repeat === "weekly"}
124
+ onChange={(e) => handleChange(e.target.value, "repeat")}
125
+ />
126
+ <CustomInput
127
+ inline
128
+ type="radio"
129
+ name="repeat"
130
+ id="repeat-custom"
131
+ label="Custom"
132
+ value="custom"
133
+ checked={schedule.repeat === "custom"}
134
+ onChange={(e) => handleChange(e.target.value, "repeat")}
135
+ />
136
+ </div>
137
+ ) : (
138
+ <div className="pl-1">{schedule.repeat === "weekly" ? "Weekly" : "Custom"}</div>
139
+ )}
140
+ </ScheduleEditorRow>
141
+ {schedule.repeat === "weekly" && (
142
+ <ScheduleEditorRow error={errors && errors.days} label="Repeat on">
143
+ <WeekSelector name="days" value={schedule.days} onChange={handleChange} />
144
+ </ScheduleEditorRow>
145
+ )}
146
+ {schedule.repeat === "custom" && (
147
+ <ScheduleEditorRow error={errors && errors.dates} label="Select dates">
148
+ <DatePicker
149
+ isDatePicker={false}
150
+ isMultiple={true}
151
+ onChange={(dates) => handleChange(dates, "dates")}
152
+ value={schedule.dates}
153
+ datePickerType="calendar"
154
+ format="YYYY-MM-DD"
155
+ />
156
+ </ScheduleEditorRow>
157
+ )}
158
+ {schedule.type === "available" && (
159
+ <Fragment>
160
+ <ScheduleEditorRow label="Time Slots">
161
+ {isNew ? (
162
+ <div>
163
+ <CustomInput
164
+ inline
165
+ type="radio"
166
+ name="departure"
167
+ id="departure-fixed"
168
+ label="Fixed Times"
169
+ value="fixed"
170
+ checked={schedule.departure === "fixed"}
171
+ onChange={(e) => handleChange(e.target.value, "departure")}
172
+ />
173
+ <CustomInput
174
+ inline
175
+ type="radio"
176
+ name="departure"
177
+ id="departure-varies"
178
+ label="No Fixed Times"
179
+ value="varies"
180
+ checked={schedule.departure === "varies"}
181
+ onChange={(e) => handleChange(e.target.value, "departure")}
182
+ />
183
+ </div>
184
+ ) : (
185
+ <div className="pl-1">{schedule.departure === "fixed" ? "Fixed Times" : "No Varies"} </div>
186
+ )}
187
+ </ScheduleEditorRow>
188
+ {schedule.departure === "fixed" && (
189
+ <ScheduleEditorRow error={errors && errors.times} label="">
190
+ <TimeSlotSelector
191
+ name="times"
192
+ value={schedule.times}
193
+ onChange={(v) => handleChange(v, "times")}
194
+ />
195
+ </ScheduleEditorRow>
196
+ )}
197
+ </Fragment>
198
+ )}
199
+ {schedule.type === "unavailable" && (
200
+ <ScheduleEditorRow label="Time ranges">
201
+ <TimeRangeSelector
202
+ error={errors && errors.timeRanges}
203
+ name="timeRanges"
204
+ value={schedule.timeRanges}
205
+ onChange={(v) => handleChange(v, "timeRanges")}
206
+ />
207
+ </ScheduleEditorRow>
208
+ )}
209
+ {schedule.repeat === "weekly" && (
210
+ <ScheduleEditorRow label="Valid from">
211
+ <div>
212
+ <DatePicker
213
+ isDatePicker={true}
214
+ onChange={(v) => handleChange(v, "start")}
215
+ minDate={today}
216
+ maxDate={new Date(schedule.end)}
217
+ value={schedule.start ? new Date(schedule.start) : null}
218
+ format="dd MMM, yyyy"
219
+ placeholder="Now"
220
+ clearButtonText="Now"
221
+ />
222
+ <span className="mx-2"> until </span>
223
+ <DatePicker
224
+ isDatePicker={true}
225
+ onChange={(v) => handleChange(v, "end")}
226
+ minDate={new Date(schedule.start)}
227
+ value={schedule.end ? new Date(schedule.end) : null}
228
+ format="dd MMM, yyyy"
229
+ placeholder="Forever"
230
+ clearButtonText="Forever"
231
+ />
232
+ </div>
233
+ </ScheduleEditorRow>
234
+ )}
235
+ {schedule.type === "available" && (
236
+ <ScheduleEditorRow label="Price" error={errors && errors.priceDelta}>
237
+ <div className="w-75 form-check-inline">
238
+ <Input
239
+ className="w-50"
240
+ type="select"
241
+ value={schedule.priceDeltaType}
242
+ onChange={(e) => handleChange(e.target.value, "priceDeltaType")}
243
+ name="priceDeltaType"
244
+ >
245
+ <option value="">No change</option>
246
+ <option value="increase">Increase by</option>
247
+ <option value="decrease">Decrease by</option>
248
+ </Input>{" "}
249
+ &nbsp;
250
+ <InputGroup
251
+ className={classNames("w-50", {
252
+ invisible: schedule.priceDeltaType === "",
253
+ })}
254
+ >
255
+ <InputGroupAddon addonType="prepend">
256
+ <InputGroupText className="bg-white">$</InputGroupText>
257
+ </InputGroupAddon>
258
+ <Input
259
+ className="border-left-0"
260
+ type="text"
261
+ name="pricedelta"
262
+ value={schedule.priceDelta}
263
+ onChange={(e) => handleChange(e.target.value, "priceDelta")}
264
+ name="priceDelta"
265
+ />
266
+ </InputGroup>
267
+ </div>
268
+ </ScheduleEditorRow>
269
+ )}
270
+ <ScheduleEditorRow label="Summary">
271
+ <span>{getScheduleSummary(schedule, price)}</span>
272
+ </ScheduleEditorRow>
273
+ </div>
274
+ );
275
+ };
276
+
277
+ export default ScheduleEditor;