cronofy-elements 1.42.0 → 1.44.1

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.
@@ -22,25 +22,6 @@
22
22
  <div id="cronofy-date-time-picker-two"></div>
23
23
  <hr>
24
24
  <div class="wrapper">
25
- <div class="column">
26
- <h2>Consectetur adipisicing elit</h2>
27
- <p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. <a href="/">Voluptate sed ex totam odit</a> quae hic voluptatibus, quisquam dicta animi laborum velit laboriosam, placeat suscipit tenetur voluptates! Illo tempora minus animi.</p>
28
- <p>Lorem ipsum, dolor sit amet consectetur adipisicing elit. Quidem consequuntur repudiandae nesciunt, totam dicta earum deleniti magnam eveniet eligendi sunt adipisci veniam esse aliquid aperiam corporis optio fuga tempora sit!</p>
29
- </div>
30
- <div class="column">
31
- <h2>Est deserunt non quis do quis nostrud ad exercitation proident incididunt.</h2>
32
- <p>Excepturi dicta neque ut, a expedita quaerat, tempore dolor placeat quibusdam cumque ipsam cum officia voluptate, ea fuga optio necessitatibus inventore tempora?</p>
33
- <p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Natus nisi eveniet voluptas provident esse beatae minus at error dignissimos neque? Temporibus velit numquam aut labore porro neque ipsum, deleniti a?</p>
34
- <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Eum sunt eligendi, error quos nulla dolorem alias corrupti iste fugiat deleniti nam unde, porro provident a voluptatem at voluptates, dolores sed!</p>
35
- </div>
36
- </div>
37
- <p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Natus nisi eveniet voluptas provident esse beatae minus at error dignissimos neque? Temporibus velit numquam aut labore porro neque ipsum, deleniti a?</p>
38
- <p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Debitis saepe maxime corporis nemo hic rerum porro in repudiandae? Libero eligendi impedit reiciendis exercitationem fuga ex eos aliquid itaque facere corrupti.</p>
39
- <hr>
40
- <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Eum sunt eligendi, error quos nulla dolorem alias corrupti iste fugiat deleniti nam unde, porro provident a voluptatem at voluptates, dolores sed!</p>
41
- <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptatum commodi fuga laboriosam? Quas officiis doloremque iure modi, ipsam dolore ad illum saepe et voluptatum quis tempore ullam dolor, sequi nulla!</p>
42
- <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Eum sunt eligendi, error quos nulla dolorem alias corrupti iste fugiat deleniti nam unde, porro provident a voluptatem at voluptates, dolores sed!</p>
43
-
44
25
  <script>
45
26
  const locale = "<%= locale %>";
46
27
 
@@ -64,10 +45,83 @@
64
45
  return output.utc().format();
65
46
  };
66
47
 
48
+ const sequencedAvailabilityQuery = {
49
+ sequence: [
50
+ {
51
+ sequence_id: "test",
52
+ ordinal: 1,
53
+ participants: [
54
+ {
55
+ required: "all",
56
+ members: [
57
+ {
58
+ sub: "<%= sub %>",
59
+ // managed_availability: true,
60
+ // availability_rule_ids: ["weekly_work_hours"]
61
+ }
62
+ ]
63
+ }
64
+ ],
65
+ required_duration: { minutes: 5 },
66
+ },
67
+ {
68
+ sequence_id: "test-1",
69
+ ordinal: 2,
70
+ participants: [
71
+ {
72
+ required: "all",
73
+ members: [
74
+ {
75
+ sub: "<%= sub %>",
76
+ // managed_availability: true,
77
+ // availability_rule_ids: ["weekly_work_hours"]
78
+ }
79
+ ]
80
+ }
81
+ ],
82
+ required_duration: { minutes: 45 },
83
+ start_interval: { minutes: 5 },
84
+ buffer: {
85
+ before: {
86
+ minimum: { minutes: 15 }
87
+ }
88
+ }
89
+ },
90
+ {
91
+ sequence_id: "test-2",
92
+ ordinal: 3,
93
+ participants: [
94
+ {
95
+ required: "all",
96
+ members: [
97
+ {
98
+ sub: "<%= sub %>",
99
+ // managed_availability: true,
100
+ // availability_rule_ids: ["weekly_work_hours"]
101
+ }
102
+ ]
103
+ }
104
+ ],
105
+ required_duration: { minutes: 30 },
106
+ start_interval: { minutes: 10 },
107
+ buffer: {
108
+ before: {
109
+ minimum: { minutes: 15 }
110
+ }
111
+ }
112
+ },
113
+
114
+ ],
115
+ query_periods: [
116
+ { start: slotTimes(01,"08:00"), end: slotTimes(31,"17:00") }
117
+ ]
118
+ }
119
+
67
120
  CronofyElements.DateTimePicker({
68
121
  element_token: "<%= date_time_picker_token %>",
69
122
  target_id: 'cronofy-date-time-picker',
70
123
  api_domain:"<%= api_domain %>",
124
+ //availability_query: sequencedAvailabilityQuery,
71
125
  availability_query: {
72
126
  // response_format: "slots",
73
127
  participants: [
@@ -152,8 +206,7 @@
152
206
  ],
153
207
  required_duration: { minutes: 30 },
154
208
  query_periods: [
155
- { start: slotTimes(1,"23:00"), end: slotTimes(2,"01:00") },
156
- { start: slotTimes(2,"23:00"), end: slotTimes(3,"01:00") }
209
+ { start: slotTimes(1,"23:00"), end: slotTimes(100,"01:00") },
157
210
  ]
158
211
  },
159
212
  config: {
@@ -162,8 +215,15 @@
162
215
  styles: {
163
216
  prefix: "SP2",
164
217
  colors: {
165
- buttonTextHover: "red",
166
- buttonHoverText: "orange"
218
+ button: "#9977FF",
219
+ buttonActive: "#5088FF",
220
+ buttonHover: "#AACCFF",
221
+ buttonHoverText: "#50AAFF",
222
+ buttonTextHover: "#AA50FF",
223
+ neutralDark: "#403040",
224
+ neutral: "#A03040",
225
+ neutralLight: "#F05080",
226
+
167
227
  }
168
228
  },
169
229
  callback: slot => console.log('callback',slot),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cronofy-elements",
3
- "version": "1.42.0",
3
+ "version": "1.44.1",
4
4
  "description": "Fast track scheduling with Cronofy's embeddable UI Elements",
5
5
  "main": "build/npm/CronofyElements.js",
6
6
  "scripts": {
@@ -45,61 +45,61 @@
45
45
  "author": "",
46
46
  "license": "ISC",
47
47
  "devDependencies": {
48
- "@babel/cli": "^7.12.10",
49
- "@babel/core": "^7.12.10",
50
- "@babel/plugin-proposal-class-properties": "^7.12.1",
51
- "@babel/plugin-proposal-object-rest-spread": "^7.12.1",
52
- "@babel/polyfill": "^7.12.1",
53
- "@babel/preset-env": "^7.12.11",
54
- "@babel/preset-react": "^7.12.10",
55
- "@emotion/babel-preset-css-prop": "^10.2.1",
56
- "@emotion/core": "^10.1.1",
57
- "@emotion/jest": "^11.3.0",
58
- "@testing-library/jest-dom": "^5.11.8",
59
- "@testing-library/react": "^11.2.2",
60
- "autoprefixer": "^10.2.5",
61
- "babel-eslint": "^10.1.0",
62
- "babel-loader": "^8.2.2",
63
- "babel-plugin-emotion": "^10.0.33",
64
- "core-js": "^3.8.2",
65
- "css-loader": "^5.2.4",
66
- "deepmerge": "^2.2.1",
67
- "ejs": "^2.7.4",
68
- "eslint": "^7.32.0",
69
- "eslint-config-prettier": "^8.3.0",
70
- "eslint-loader": "^4.0.2",
71
- "eslint-plugin-import": "^2.24.1",
72
- "eslint-plugin-jsx-a11y": "^6.4.1",
73
- "eslint-plugin-prettier": "^3.4.1",
74
- "eslint-plugin-react": "^7.24.0",
75
- "eslint-plugin-unused-imports": "^1.1.4",
76
- "express": "^4.16.4",
77
- "fs": "0.0.1-security",
78
- "git-revision-webpack-plugin": "^5.0.0",
79
- "jest": "^26.6.3",
80
- "jest-fetch-mock": "^3.0.3",
81
- "moment": "^2.29.1",
82
- "moment-timezone": "^0.5.32",
83
- "moment-timezone-data-webpack-plugin": "^1.5.0",
84
- "node-fetch": "^2.3.0",
85
- "node-sass": "^6.0.0",
86
- "path": "^0.12.7",
87
- "polished": "^2.3.3",
88
- "postcss-loader": "^5.3.0",
89
- "prettier": "2.0.5",
90
- "react": "^16.14.0",
91
- "react-custom-scrollbars": "^4.2.1",
92
- "react-day-picker": "^7.4.8",
93
- "react-dom": "^16.14.0",
94
- "react-emotion": "^9.2.12",
95
- "react-select": "^2.4.2",
96
- "react-transition-group": "^4.4.1",
97
- "sass-loader": "^11.1.1",
98
- "style-loader": "^2.0.0",
99
- "terser-webpack-plugin": "^5.1.2",
100
- "webpack": "^5.37.0",
101
- "webpack-bundle-analyzer": "^4.4.1",
102
- "webpack-cli": "^4.7.0",
103
- "whatwg-fetch": "^3.5.0"
48
+ "@babel/cli": "^7.16",
49
+ "@babel/core": "^7.12",
50
+ "@babel/plugin-proposal-class-properties": "^7.12",
51
+ "@babel/plugin-proposal-object-rest-spread": "^7.12",
52
+ "@babel/polyfill": "^7.12",
53
+ "@babel/preset-env": "^7.12",
54
+ "@babel/preset-react": "^7.12",
55
+ "@emotion/babel-preset-css-prop": "^10.2",
56
+ "@emotion/core": "^10.1",
57
+ "@emotion/jest": "^11.3",
58
+ "@testing-library/jest-dom": "^5.11",
59
+ "@testing-library/react": "^11.2",
60
+ "autoprefixer": "^10.2",
61
+ "babel-eslint": "^10.1",
62
+ "babel-loader": "^8.2",
63
+ "babel-plugin-emotion": "^10.0",
64
+ "core-js": "^3.8",
65
+ "css-loader": "^5.2",
66
+ "deepmerge": "^2.2",
67
+ "ejs": "^2.7",
68
+ "eslint": "^7.32",
69
+ "eslint-config-prettier": "^8.3",
70
+ "eslint-loader": "^4.0",
71
+ "eslint-plugin-import": ">2.24",
72
+ "eslint-plugin-jsx-a11y": ">6.4",
73
+ "eslint-plugin-prettier": ">3.4",
74
+ "eslint-plugin-react": ">7.24",
75
+ "eslint-plugin-unused-imports": ">1.1",
76
+ "express": "^4.16",
77
+ "fs": ">0.0.1-security",
78
+ "git-revision-webpack-plugin": "^5.0",
79
+ "jest": "^26.6",
80
+ "jest-fetch-mock": "^3.0",
81
+ "moment": "^2.29",
82
+ "moment-timezone": "^0.5",
83
+ "moment-timezone-data-webpack-plugin": "^1.5",
84
+ "node-fetch": "^2.3",
85
+ "node-sass": "^6.0",
86
+ "path": "^0.12",
87
+ "polished": "^2.3",
88
+ "postcss-loader": "^5.3",
89
+ "prettier": "2.0",
90
+ "react": "^16.14",
91
+ "react-custom-scrollbars": "^4.2",
92
+ "react-day-picker": "^7.4",
93
+ "react-dom": "^16.14",
94
+ "react-emotion": "^9.2",
95
+ "react-select": "^2.4",
96
+ "react-transition-group": "^4.4",
97
+ "sass-loader": "^12.1",
98
+ "style-loader": "^3.0",
99
+ "terser-webpack-plugin": "^5.1",
100
+ "webpack": "^5.37",
101
+ "webpack-bundle-analyzer": "^4.4",
102
+ "webpack-cli": "^4.7",
103
+ "whatwg-fetch": "^3.5"
104
104
  }
105
105
  }
@@ -6,6 +6,7 @@ import {
6
6
  parseTzList,
7
7
  getInitialSelectedTzid,
8
8
  getMonthsLoadingFromQuery,
9
+ getDurationFromQuery,
9
10
  } from "./utils/slots";
10
11
  import { getMonthsInDisplay, parseTimeSlots } from "./utils/calendar";
11
12
 
@@ -30,6 +31,8 @@ const DateTimePicker = ({ options }) => {
30
31
  selectedDateObject?.format("YYYY-MM")
31
32
  );
32
33
 
34
+ const duration = getDurationFromQuery(options.query);
35
+
33
36
  const endDateObject = moment(options.config.endDate, "YYYY-MM-DD");
34
37
  const startDateObject = moment(options.config.startDate, "YYYY-MM-DD");
35
38
  const currentMonthObject = selectedDateObject ?? startDateObject;
@@ -61,6 +64,7 @@ const DateTimePicker = ({ options }) => {
61
64
  : () => options.log.info("No `callback` option has been provided"),
62
65
  columnView: "loading", // loading | error | slots | confirm | no-slots
63
66
  daySlots: [],
67
+ duration,
64
68
  locale: options.locale,
65
69
  mode: options.config.mode, // confirm (default) | no_confirm
66
70
  query: options.query,
@@ -75,6 +79,7 @@ const DateTimePicker = ({ options }) => {
75
79
  endDateObject,
76
80
  focusedSlot: false,
77
81
  availableDays: [],
82
+ sequenced_availability: options.query.sequence ? true : false,
78
83
  slots: {},
79
84
  slotFetchCount: 0,
80
85
  slotInjectionPoint: undefined,
@@ -17,9 +17,11 @@ const Details = ({ duration, locale }) => {
17
17
  <TimeZoneSelector locale={locale} />
18
18
  </div>
19
19
  <div className={theme.classBuilder("details--duration")}>
20
- <p>
21
- <strong>{i18n.t("duration_label")}:</strong> {duration} {i18n.t("minutes")}
22
- </p>
20
+ {duration && (
21
+ <p>
22
+ <strong>{i18n.t("duration_label")}:</strong> {duration} {i18n.t("minutes")}
23
+ </p>
24
+ )}
23
25
  </div>
24
26
  </div>
25
27
  );
@@ -0,0 +1,71 @@
1
+ import React, { useEffect, useRef } from "react";
2
+ import moment from "moment-timezone";
3
+
4
+ import { useI18n } from "../../contexts/i18n-context";
5
+ import { useStatus } from "./contexts/status-context";
6
+ import { useTheme } from "./contexts/theme-context";
7
+ import { useTz } from "./contexts/tz-context";
8
+
9
+ const SequencedSlotButton = ({ slot }) => {
10
+ const i18n = useI18n();
11
+ const theme = useTheme();
12
+ const [status, dispatchStatus] = useStatus();
13
+ const [tz] = useTz();
14
+
15
+ const slotButtonRef = useRef();
16
+
17
+ const startArray = slot.map(a => a.start);
18
+ const endArray = slot.map(a => a.end);
19
+
20
+ const start = startArray.reduce((prev, current) => {
21
+ return prev < current ? prev : current;
22
+ });
23
+
24
+ const end = endArray.reduce((prev, current) => {
25
+ return prev > current ? prev : current;
26
+ });
27
+
28
+ const handleSlotSelection = slot => {
29
+ const selectedSlot = {
30
+ start,
31
+ end,
32
+ sequence: slot,
33
+ };
34
+ dispatchStatus({ type: "SELECT_SLOT", slot: selectedSlot, tzid: tz.selectedTzid.tzid });
35
+ };
36
+
37
+ useEffect(() => {
38
+ if (status.focusedSlot === start) {
39
+ slotButtonRef.current.focus();
40
+ }
41
+ }, [status.focusedSlot]);
42
+
43
+ let classStub = "time-slot";
44
+ if (status.selected.start === start) classStub = classStub + " time-slot--selected";
45
+
46
+ return (
47
+ <button
48
+ className={theme.classBuilder(classStub)}
49
+ onClick={() => handleSlotSelection(slot)}
50
+ ref={slotButtonRef}
51
+ >
52
+ <span className={theme.classBuilder("visually-hidden")}>
53
+ {i18n.t("select_time_slot")}
54
+ </span>
55
+ {i18n.customFormatedTimeZone(
56
+ moment(start, "YYYY-MM-DDTHH:mm:00Z"),
57
+ tz.selectedTzid.tzid,
58
+ "LT"
59
+ )}
60
+ {" - "}
61
+ {i18n.customFormatedTimeZone(
62
+ moment(end, "YYYY-MM-DDTHH:mm:00Z"),
63
+ tz.selectedTzid.tzid,
64
+ "LT"
65
+ )}{" "}
66
+ {`(${moment(start, "YYYY-MM-DDTHH:mm:00Z").tz(tz.selectedTzid.tzid).format("z")})`}
67
+ </button>
68
+ );
69
+ };
70
+
71
+ export default SequencedSlotButton;
@@ -1,6 +1,7 @@
1
1
  import React from "react";
2
2
 
3
3
  import SlotButton from "./SlotButton";
4
+ import SequencedSlotButton from "./SequencedSlotButton";
4
5
 
5
6
  import { useI18n } from "../../contexts/i18n-context";
6
7
  import { useStatus } from "./contexts/status-context";
@@ -14,7 +15,11 @@ const SlotsList = () => {
14
15
  const renderSlots = status.daySlots.map((slot, key) => {
15
16
  return (
16
17
  <li key={key} className={theme.classBuilder("slot-list--item")}>
17
- <SlotButton slot={slot} />
18
+ {status.sequenced_availability ? (
19
+ <SequencedSlotButton slot={slot} />
20
+ ) : (
21
+ <SlotButton slot={slot} />
22
+ )}
18
23
  </li>
19
24
  );
20
25
  });
@@ -1,7 +1,7 @@
1
1
  import React, { useEffect, useRef, useState } from "react";
2
2
  import moment from "moment";
3
3
 
4
- import { getSlots } from "./utils/slots";
4
+ import { getSequencedSlots, getSlots } from "./utils/slots";
5
5
 
6
6
  import Calendar from "./Calendar";
7
7
  import Confirm from "./Confirm";
@@ -31,26 +31,20 @@ const Wrapper = () => {
31
31
  }
32
32
 
33
33
  const fetchMonthSlots = (query, month) => {
34
- return getSlots({
34
+ const fetch = status.sequenced_availability ? getSequencedSlots : getSlots;
35
+
36
+ return fetch({
35
37
  query,
36
38
  auth: status.auth,
37
39
  tzid: status.tzid,
38
- })
39
- .then(res => {
40
- dispatchStatus({
41
- type: "SET_SLOTS",
42
- slots: res,
43
- tzid: tz.selectedTzid.tzid,
44
- month: month,
45
- });
46
- })
47
- .catch(res => {
48
- dispatchStatus({
49
- type: "ERROR_LOADING_SLOTS",
50
- error: res,
51
- month: month,
52
- });
40
+ }).then(res => {
41
+ dispatchStatus({
42
+ type: "SET_SLOTS",
43
+ slots: res,
44
+ tzid: tz.selectedTzid.tzid,
45
+ month: month,
53
46
  });
47
+ });
54
48
  };
55
49
 
56
50
  const currentMonth = status.months.find(m => m.current);
@@ -60,7 +54,15 @@ const Wrapper = () => {
60
54
  .then(() => {
61
55
  // Get slots for remianing months
62
56
  const remainingMonths = status.months.filter(month => !month.current);
63
- remainingMonths.forEach(month => fetchMonthSlots(month.query, month.month));
57
+ remainingMonths.forEach(month =>
58
+ fetchMonthSlots(month.query, month.month).catch(res => {
59
+ dispatchStatus({
60
+ type: "ERROR_LOADING_SLOTS",
61
+ error: res,
62
+ month: month.month,
63
+ });
64
+ })
65
+ );
64
66
  })
65
67
  .catch(error => {
66
68
  dispatchStatus({ type: "ERROR_GETTING_SLOTS", error });
@@ -172,7 +174,7 @@ const Wrapper = () => {
172
174
 
173
175
  return (
174
176
  <section className={theme.classBuilder()} style={theme.customProperties}>
175
- <Details duration={status.query.required_duration.minutes} locale={status.locale} />
177
+ <Details duration={status.duration} locale={status.locale} />
176
178
  <div className={theme.classBuilder("wrapper")}>
177
179
  <div
178
180
  className={
@@ -6,6 +6,7 @@ import {
6
6
  getAvailableDays,
7
7
  getLocalDayFromUtc,
8
8
  removeMonthFromLoading,
9
+ addSequencedSlotsToObject,
9
10
  } from "../utils/slots";
10
11
  import { getMonthsInDisplay, parseTimeSlots } from "../utils/calendar";
11
12
 
@@ -43,6 +44,7 @@ export const statusReducer = (state, action) => {
43
44
  notification: {
44
45
  type: "error",
45
46
  message: "There was an error getting the slots",
47
+ body: action.error.body,
46
48
  },
47
49
  };
48
50
  state.callback(notification);
@@ -66,6 +68,12 @@ export const statusReducer = (state, action) => {
66
68
  }
67
69
 
68
70
  case "NO_SLOTS_FOUND": {
71
+ const notification = {
72
+ notification: {
73
+ type: "no_slots_found",
74
+ },
75
+ };
76
+ state.callback(notification);
69
77
  return { ...state, columnView: "no-slots" };
70
78
  }
71
79
 
@@ -91,21 +99,31 @@ export const statusReducer = (state, action) => {
91
99
 
92
100
  case "SET_SLOTS": {
93
101
  if (!action.slots.length > 0) {
94
- return state;
102
+ const monthsLoading = removeMonthFromLoading(state.monthsLoading, action.month);
103
+ return {
104
+ ...state,
105
+ monthsLoading,
106
+ slotFetchCount: state.slotFetchCount + 1,
107
+ };
95
108
  }
96
109
 
97
- const slotsObject = addSlotsToObject(state.slots, action.slots);
98
- const availableDays = getAvailableDays(addSlotsToObject({}, action.slots), action.tzid);
99
- const availableDaysSet = new Set([...state.availableDays, ...availableDays]);
110
+ const slotsObject = state.sequenced_availability
111
+ ? addSequencedSlotsToObject(state.slots, action.slots)
112
+ : addSlotsToObject(state.slots, action.slots);
113
+ const availableDays = getAvailableDays(slotsObject, action.tzid);
100
114
  const monthsLoading = removeMonthFromLoading(state.monthsLoading, action.month);
101
115
 
116
+ const injectionPoint = state.sequenced_availability
117
+ ? getLocalDayFromUtc(action.slots[0].sequence[0].start, action.tzid)
118
+ : getLocalDayFromUtc(action.slots[0].start, action.tzid);
119
+
102
120
  return {
103
121
  ...state,
104
- availableDays: [...availableDaysSet].sort(),
122
+ availableDays,
105
123
  monthsLoading,
106
124
  slots: slotsObject,
107
125
  slotFetchCount: state.slotFetchCount + 1,
108
- slotInjectionPoint: getLocalDayFromUtc(action.slots[0].start, action.tzid),
126
+ slotInjectionPoint: injectionPoint,
109
127
  };
110
128
  }
111
129
 
@@ -118,13 +136,17 @@ export const statusReducer = (state, action) => {
118
136
 
119
137
  case "SELECT_DAY": {
120
138
  const daySlots = getSlotsByDay(state.slots, action.day, action.tzid);
121
- const focusedSlot = daySlots[0];
139
+ const focusedSlot = state.sequenced_availability
140
+ ? daySlots[0]?.reduce((prev, current) => {
141
+ return prev.start < current.start ? prev.start : current.start;
142
+ })
143
+ : daySlots[0]?.start;
122
144
 
123
145
  return {
124
146
  ...state,
125
147
  selectedDay: action.day,
126
148
  focusedDay: action.day,
127
- focusedSlot: focusedSlot?.start,
149
+ focusedSlot: focusedSlot,
128
150
  columnView: "slots",
129
151
  daySlots,
130
152
  populated: true,