@tulipnpm/timekit_project_selector 2.1.0-rc.0 → 2.1.0-rc.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.
package/README.md CHANGED
@@ -1,22 +1,23 @@
1
- # TimeKit Project Selector
1
+ # Tulip Appointments Web Widget
2
2
 
3
- The TimeKit Project Selector is a library that extends the functionality of [TimeKit's Booking Widget](https://developers.timekit.io/docs/booking-widget-v2). This library allows users to set up selectors based on project metadata, which will filter down a list of TimeKit projects for a customer to book with. The library has a default UI that embeds on an e-commerce site that can either be customized or overwritten.
3
+ The Tulip Appointments Web Widget is a library that extends the functionality of [TimeKit's Booking Widget](https://developers.timekit.io/docs/booking-widget-v2). This library allows users to set up selectors based on project metadata, which will filter down a list of TimeKit projects for a customer to book with. The library has a default UI that embeds on an e-commerce site that can either be customized or overwritten.
4
4
 
5
5
  ## Installation
6
6
 
7
7
  ```html
8
- <script src="https://cdn.jsdelivr.net/npm/@tulipnpm/timekit_project_selector@latest/dist/timekit_project_selector.min.js"></script>
9
8
  <link rel="stylesheet" href="https://cdn.timekit.io/booking-js/v3/booking.min.css" />
10
9
  <script type="text/javascript" src="//cdn.timekit.io/booking-js/v3/booking.min.js" defer></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/@tulipnpm/timekit_project_selector@latest/dist/timekit_project_selector.min.js"></script>
11
11
  ```
12
12
 
13
13
  ## Initialization
14
14
 
15
- To initialize the TimeKit Project Selector on your site, you first need to import all required libraries onto your webpage (see above). After the library is installed, set up is as easy as running a single function:
15
+ To initialize the Tulip Appointments Web Widget on your site, you first need to import all required libraries onto your webpage (see above). After the library is installed, set up is as easy as running a single function:
16
16
 
17
17
  ```js
18
18
  timekit_project_selector.init({
19
19
  app_key: <timekit_app_key>,
20
+ api_base_url: <timekit_api_url>
20
21
  }).then(() => {
21
22
  // Your code here...
22
23
  });
@@ -28,18 +29,91 @@ This will connect to TimeKit's API and load in all the required data. Once the i
28
29
 
29
30
  ## Configurations
30
31
 
31
- TimeKit Project Selector has many configuration options:
32
+ Tulip Appointments Web Widget has many configuration options:
32
33
 
33
34
  | Option | Optional? | Default value | Description |
34
35
  |--------|-----------|---------------|-------------|
35
36
  | app_key | No | | Token required to connect to TimeKit API. Can be found at **https://admin.timekit.io/a/apps/<app_slug>/apisettings/keys** |
36
- | defaultUI | Yes | true | When true, will create the default user interface for the project selector. More details below |
37
- | embed | Yes | false | When true, the user interface will not be shown in the widget, it will be placed inside of a specified div. More details below |
37
+ | api_base_url | Yes | https://api.timekit.io | Timekit API url pointing to production environment. For staging environments point to `https://api-staging.timekit.io`.
38
+ | defaultUI | Yes | true | When true, will create the default user interface for the project selector. See [Widget Modes](#widget-modes) below |
39
+ | embed | Yes | false | When true, the user interface will be configured for Page Mode. It will be placed inside of a specified div. See [Widget Modes](#widget-modes) below |
38
40
  | includePrivateAppointments | Yes | false | When true, private appointment types will be fetched from the TimeKit API |
39
41
  | region | Yes | | Initial filter applied when getting default list of projects. Any project that does not have the same **t_region** metadata value will not be shown on initialization |
40
42
  | selectorOptions | No | | See below for details |
41
43
  | widgetImageUrl | Yes | Tulip Appointments Icon | When using the default widget UI, change this value to your desired image URL to replace widget image |
42
44
  | duplicateCustomerCheck | Yes | false | When a new booking is created, a call is made to check for potential duplicate Timekit customers. Requires the following webhook to be configured via Timekit: **/api/customers/timekit_webhook_connect_client**|
45
+ | shouldConsiderAssociateAvailability | Yes | false | When enabled, this configuration ensures that service availability considers store associate schedules, returning only times when associates are available.|
46
+ | shouldAutomaticallyBookAssociates | Yes | false | When enabled, associates are automatically assigned to bookings. |
47
+
48
+ ### Widget Modes
49
+
50
+ There are three modes of UI the Tulip Appointments Web Widget:
51
+
52
+ - Popup Mode
53
+ - Automatically creates a button that will open the widget in a popup
54
+ - Page Mode
55
+ - Injects itself onto the page given a destination HTML element
56
+ - Custom UI
57
+ - No UI is created, you have to create UI for all steps up to the calendar view
58
+
59
+ Whichever mode you choose is determined by the `defaultUI` and `embed` properties. Refer to the matrix below to see which mode is configured for each possible value of `defaultUI ` and `embed`.
60
+
61
+
62
+ | | embed=true | embed=false |
63
+ |-|-|-|
64
+ | defaultUI=true | Page Mode | Popup Mode |
65
+ | defaultUI=false| Custom UI | Custom UI |
66
+
67
+ #### Popup Mode Widget
68
+
69
+ ![image](https://github.com/user-attachments/assets/7b04ed86-f724-4927-a86a-c95b4f299774)
70
+
71
+ When enabled on initialization of the library the default user interface will be built on the web page as a widget. To disable the default UI from appearing, simply set the **defaultUI** configuration to `false`.
72
+
73
+ NOTE: If you are using the widget view, you cannot have a div with ID **timekit-project-selector-container** on your DOM.
74
+
75
+ ```js
76
+ timekit_project_selector.init({
77
+ app_key: <timekit_app_key>,
78
+ defaultUI: true,
79
+ });
80
+ ```
81
+
82
+ #### Page Mode Widget
83
+
84
+ ![image](https://github.com/user-attachments/assets/e92b7b9b-fae8-4d96-a503-86f053382b37)
85
+
86
+ To use the Page Mode Widget, change the **embed** configuration to `true`. This will build the widget into a container div with the id **timekit-project-selector-container**. This allows you control of the positioning, size and style of the interface.
87
+
88
+ ```html
89
+ <div id="timekit-project-selector-container"></div>
90
+ <script>
91
+ timekit_project_selector.init({
92
+ app_key: <timekit_app_key>,
93
+ defaultUI: true,
94
+ embed: true,
95
+ });
96
+ </script>
97
+ ```
98
+
99
+ #### Custom UI
100
+
101
+ If you do not want to use the Page Mode or Popup Mode widget and want to create your own UI, change the **defaultUI** configuration to `false`. This will not show the Popup or Page Widget UIs. When the store and appointment type is selected, a calendar UI will be populated in the HTML element with id `bookingjs`.
102
+
103
+ For a more in-depth example, see [Example Custom UI](#example-custom-ui)
104
+
105
+
106
+ ```html
107
+ <!-- Container div for the calendar to display-->
108
+ <div id="bookingjs"></div>
109
+ <script>
110
+ timekit_project_selector.init({
111
+ app_key: <timekit_app_key>,
112
+ defaultUI: false,
113
+ });
114
+ </script>
115
+ ```
116
+
43
117
 
44
118
  ### Selector Options
45
119
 
@@ -51,11 +125,11 @@ timekit_project_selector.init({
51
125
  ...,
52
126
  selectorOptions: {
53
127
  store_project: true,
54
- store_appointment_type_project: true,
128
+ service_project: true,
55
129
  }
56
130
  });
57
131
  ```
58
- > You must use project type as key in the selectorOptions. Ex. global_appointment_type_project for render Global Appointment Type in step, store_appointment_type_project for render Store Appointment Type in step, store_project for render Stores in step
132
+ > You must use project type as key in the selectorOptions. Ex. service_project for render Global Appointment Type in step, store_appointment_type_project for render Store Appointment Type in step, store_project for render Stores in step
59
133
 
60
134
  ### Selector Option Copyright
61
135
 
@@ -65,7 +139,7 @@ When using the default UI, you must set the selector display values for each sel
65
139
 
66
140
  #### Selector Option Strategy (Optional)
67
141
 
68
- This parameter is used for defining the logic of rendering projects. The **strategy** field is used for rendering either Stores having Appointment Types belonging to them, or Appointment Types having Stores belonging to them. By default, strategy is set to `store_project` (Appointment Type > Store)
142
+ This parameter is used for defining the logic of rendering projects. The **strategy** field is used for rendering either Stores having Appointment Types belonging to them, or Appointment Types having Stores belonging to them. By default, strategy is set to `store_project` (Appointment Type > Store)
69
143
 
70
144
  ```
71
145
  strategy: 'store_project',
@@ -73,7 +147,7 @@ strategy: 'store_project',
73
147
 
74
148
  #### Selector Option Search Bar
75
149
 
76
- In addition to allowing for customization of the text on the widget, the Timekit Project Selector also allows for the addition of a search bar to allow a user to search for certain text inside of a card.
150
+ In addition to allowing for customization of the text on the widget, the Tulip Appointments Web Widget also allows for the addition of a search bar to allow a user to search for certain text inside of a card.
77
151
 
78
152
  To enable this search bar, you can include the following inside of a Selector Option.
79
153
 
@@ -84,7 +158,7 @@ search_bar: {
84
158
  }
85
159
  ```
86
160
 
87
- By default, the search bar feature is not enabled if this configuration is missing from a selector option. If this configuration is present, then the **enabled** field is required. The **enabled** field must either be `true` or `false`, where `true` indicates that the search bar will show up in the default UI, and `false` indicates that it will not be present.
161
+ By default, the search bar feature is not enabled if this configuration is missing from a selector option. If this configuration is present, then the **enabled** field is required. The **enabled** field must either be `true` or `false`, where `true` indicates that the search bar will show up in the default UI, and `false` indicates that it will not be present.
88
162
 
89
163
  The **placeholder** field is optional and is not required for the search bar. If this field is missing, the placeholder text inside the search bar will default to `Please enter your search term`. Otherwise, the text can be replaced following the specifications below.
90
164
 
@@ -133,12 +207,13 @@ Here is a sample of combined selectorOptions.
133
207
  timekit_project_selector.init({
134
208
  ...,
135
209
  selectorOptions: {
136
- global_appointment_type_project: {
210
+ service_project: {
211
+ strategy: 'service_project',
212
+ card_title: `{{[project]name}}`,
137
213
  title: 'Select an Appointment Type',
214
+ card_image: `{{[project]image_url}}`,
215
+ card_body: `{{[project]description}}`,
138
216
  description: 'Which appointment type would you like?',
139
- card_image: '[meta]t_appointment_type_image',
140
- card_title: '[meta]t_appointment_type_name',
141
- card_body: '[meta]t_appointment_type_description',
142
217
  card_footer: '<i class="far fa-clock"></i> {{[project]availability.length}}',
143
218
  filters: {
144
219
  't_disabled': 0,
@@ -147,86 +222,79 @@ timekit_project_selector.init({
147
222
  },
148
223
  store_project: {
149
224
  title: 'Select a Store',
150
- description: 'Choose a store from the following that you will be visiting for your appointment.',
225
+ strategy: 'store_project',
151
226
  card_title: '[meta]t_store_name',
152
- card_body: `{{[meta]t_store_address}}, {{[meta]t_store_city}}`,
153
227
  card_footer: '[meta]t_store_phone',
228
+ card_body: `{{[meta]t_store_address}}, {{[meta]t_store_city}}`,
229
+ description: 'Choose a store from the following that you will be visiting for your appointment.',
154
230
  search_bar: {
155
231
  enabled: true,
156
232
  placeholder: 'Search for a city or postal code'
157
233
  },
158
- strategy: 'store_project',
159
234
  },
160
235
  }
161
236
  });
162
237
  ```
163
238
 
164
- ### Default UI
165
239
 
166
- When enabled on initialization of the library the default user interface will be built on the web page as a widget. To disable the default UI from appearing, simply set the **defaultUI** configuration to `false`.
167
240
 
168
- NOTE: If you are using the widget view, you cannot have a div with ID **timekit-project-selector-container** on your DOM.
169
-
170
- ```js
171
- timekit_project_selector.init({
172
- app_key: <timekit_app_key>,
173
- defaultUI: true,
174
- });
175
- ```
176
-
177
- ### Embedding UI
241
+ ### Including Private Appointment Types (Optional)
178
242
 
179
- With the default UI there are two options: Widget Mode and Embedded Mode. By default, the UI will build a widget. To use the embedded mode, change the **embed** configuration to `true`. This will not build the widget, but will build in interface into a container div with the id **timekit-project-selector-container**. This allows you control of the positioning, size and style of the interface.
243
+ By default widget fetches only public appointment types, in order to change that behavior and fetch the private appointment types, `includePrivateAppointments` configuration option can be specified to `true`.
180
244
 
181
245
  ```js
182
- // Requires a div with ID 'timekit-project-selector-container'. This is where the UI will render.
183
246
  timekit_project_selector.init({
184
247
  app_key: <timekit_app_key>,
185
- defaultUI: true,
186
- embed: true,
248
+ ...,
249
+ includePrivateAppointments: true,
187
250
  });
188
251
  ```
189
252
 
190
- ### Custom UI
191
-
192
- If you do not want to use the default UI and want to create your own UI, change the **defaultUI** configuration to `false`. This will not show the widget or embedded UI.
253
+ In case if you use custom UI and you need to pull both private and public appointment types from TimeKit API, but want to display the private appointment types only when needed, filters can be used:
193
254
 
194
255
  ```js
195
256
  timekit_project_selector.init({
196
257
  app_key: <timekit_app_key>,
197
258
  defaultUI: false,
259
+ includePrivateAppointments: true,
260
+ selectorOptions: {
261
+ service_project: true,
262
+ store_project: true,
263
+ }
264
+ }).then(() => {
265
+ // Add this filter whenever you want to surface only public appointment types
266
+ globalProjectFilters['t_private'] = 0;
198
267
  });
199
268
  ```
200
269
 
201
- ### Including Private Appointment Types (Optional)
270
+ ### Include Associate Schedules In Project Availability (Optional)
202
271
 
203
- By default widget fetches only public appointment types, in order to change that behavior and fetch the private appointment types, `includePrivateAppointments` configuration option can be specified to `true`.
272
+ By default, the widget does not include associates availability when displaying services availability. To include associates availability, the `shouldConsiderAssociateAvailability` configuration option can be set to `true`.
204
273
 
205
274
  ```js
206
275
  timekit_project_selector.init({
207
276
  app_key: <timekit_app_key>,
208
277
  ...,
209
- includePrivateAppointments: true,
278
+ shouldConsiderAssociateAvailability: true,
210
279
  });
211
280
  ```
212
281
 
213
- In case if you use custom UI and you need to pull both private and public appointment types from TimeKit API, but want to display the private appointment types only when needed, filters can be used:
282
+ ---
283
+
284
+ ### Automatically Assign Associates To Bookings (Optional)
285
+
286
+ Once bokings are created associates must be manually assigned to those bookings. This automatically assigns assigns associates when bookings are created. To automatically assign associates to bookings the `shouldAutomaticallyBookAssociates` configuration option can be set to `true`.
214
287
 
215
288
  ```js
216
289
  timekit_project_selector.init({
217
290
  app_key: <timekit_app_key>,
218
- defaultUI: false,
219
- includePrivateAppointments: true,
220
- selectorOptions: {
221
- global_appointment_type_project: true,
222
- store_project: true,
223
- }
224
- }).then(() => {
225
- // Add this filter whenever you want to surface only public appointment types
226
- globalProjectFilters['t_private'] = 0;
291
+ ...,
292
+ shouldAutomaticallyBookAssociates: true,
227
293
  });
228
294
  ```
229
295
 
296
+ ---
297
+
230
298
  ### Search For Potential Duplicate Timekit Customers (Optional)
231
299
 
232
300
  By default, the widget does not check for potential duplicate customers when creating a new booking. To check for potential duplicate customers, the `duplicateCustomerCheck` configuration option can be set to `true`. A webhook must also be configured via the [Admin Dashboard](https://admin.timekit.io/) in Timekit under API Settings with the following url: **https://**your_server_url**/api/customers/timekit_webhook_connect_client**.
@@ -247,11 +315,11 @@ See below for how to use the exposed methods.
247
315
 
248
316
  ## Exposed Methods
249
317
 
250
- The TimeKit Project Selector library exposes the methods that are used to create our default user interface. This gives you the tools to build custom interfaces that are branded for your company to have customers select the correct TimeKit project to book with.
318
+ The Tulip Appointments Web Widget library exposes the methods that are used to create our default user interface. This gives you the tools to build custom interfaces that are branded for your company to have customers select the correct TimeKit project to book with.
251
319
 
252
320
  ### init
253
321
 
254
- Asynchronous method that initiate the TimeKit Project Selector.
322
+ Asynchronous method that initiate the Tulip Appointments Web Widget.
255
323
 
256
324
  ```js
257
325
  timekit_project_selector.init({
@@ -277,10 +345,7 @@ Optionally takes project type for return projects by project type only.(Filters
277
345
  Optionally takes filters for return filtered projects
278
346
 
279
347
  ```js
280
- let filters = [];
281
- filters['key'] = 'value';
282
-
283
- timekit_project_selector.getStrategy().getProjects('global_appointment_type', filters);
348
+ await timekit_project_selector.getStrategy('store_project').getProjects({ id: 'ef0cee8a-6096-453f-a634-1597b359cdfa'});
284
349
  ```
285
350
 
286
351
  ### getFilters
@@ -320,12 +385,13 @@ timekit_project_selector.selectProject(timekitProject);
320
385
  ### Global Appointment Type -> Store -> Booking.js
321
386
  ```js
322
387
  selectorOptions: {
323
- global_appointment_type_project: {
388
+ service_project: {
389
+ strategy: 'service_project',
390
+ card_title: `{{[project]name}}`,
324
391
  title: 'Select an Appointment Type',
392
+ card_image: `{{[project]image_url}}`,
393
+ card_body: `{{[project]description}}`,
325
394
  description: 'Which appointment type would you like?',
326
- card_image: '[meta]t_appointment_type_image',
327
- card_title: '[meta]t_appointment_type_name',
328
- card_body: '[meta]t_appointment_type_description',
329
395
  card_footer: '<i class="far fa-clock"></i> {{[project]availability.length}}',
330
396
  filters: {
331
397
  't_disabled': 0,
@@ -334,15 +400,15 @@ timekit_project_selector.selectProject(timekitProject);
334
400
  },
335
401
  store_project: {
336
402
  title: 'Select a Store',
337
- description: 'Choose a store from the following that you will be visiting for your appointment.',
403
+ strategy: 'store_project',
338
404
  card_title: '[meta]t_store_name',
339
- card_body: `{{[meta]t_store_address}}, {{[meta]t_store_city}}`,
340
405
  card_footer: '[meta]t_store_phone',
406
+ card_body: `{{[meta]t_store_address}}, {{[meta]t_store_city}}`,
407
+ description: 'Choose a store from the following that you will be visiting for your appointment.',
341
408
  search_bar: {
342
409
  enabled: true,
343
410
  placeholder: 'Search for a city or postal code'
344
411
  },
345
- strategy: 'store_project'
346
412
  }
347
413
  }
348
414
  ```
@@ -352,10 +418,11 @@ timekit_project_selector.selectProject(timekitProject);
352
418
  selectorOptions: {
353
419
  store_project: {
354
420
  title: 'Select a Store',
355
- description: 'Choose a store from the following that you will be visiting for your appointment.',
421
+ strategy: 'store_project',
356
422
  card_title: '[meta]t_store_name',
357
- card_body: `{{[meta]t_store_address}}, {{[meta]t_store_city}}`,
358
423
  card_footer: '[meta]t_store_phone',
424
+ card_body: `{{[meta]t_store_address}}, {{[meta]t_store_city}}`,
425
+ description: 'Choose a store from the following that you will be visiting for your appointment.',
359
426
  search_bar: {
360
427
  enabled: true,
361
428
  placeholder: 'Search for a city or postal code'
@@ -363,11 +430,12 @@ timekit_project_selector.selectProject(timekitProject);
363
430
  //strategy: 'store_project'
364
431
  },
365
432
  store_appointment_type_project: {
433
+ strategy: 'service_project',
434
+ card_title: `{{[project]name}}`,
366
435
  title: 'Select an Appointment Type',
436
+ card_image: `{{[project]image_url}}`,
437
+ card_body: `{{[project]description}}`,
367
438
  description: 'Which appointment type would you like?',
368
- card_image: '[meta]t_appointment_type_image',
369
- card_title: '[meta]t_appointment_type_name',
370
- card_body: '[meta]t_appointment_type_description',
371
439
  card_footer: '<i class="far fa-clock"></i> {{[project]availability.length}}',
372
440
  filters: {
373
441
  't_disabled': 0,
@@ -378,27 +446,30 @@ timekit_project_selector.selectProject(timekitProject);
378
446
  ```
379
447
 
380
448
  ## Example Custom UI
449
+
381
450
  ```html
382
451
  <!DOCTYPE html>
452
+
383
453
  <html lang="en">
384
454
 
385
455
  <head>
386
456
  <meta charset="UTF-8">
387
457
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
388
- <title>Custom UI - TimeKit Project Selector Example</title>
458
+ <title>Custom UI - Tulip Appointments Web Widget Example</title>
389
459
 
390
460
  <!-- Booking.js minified dependency -->
391
461
  <link rel="stylesheet" href="https://cdn.timekit.io/booking-js/v3/booking.min.css" />
392
- <script type="text/javascript" src="//cdn.timekit.io/booking-js/v3/booking.min.js" defer></script>
462
+ <script type="text/javascript" src="https://cdn.timekit.io/booking-js/v3/booking.min.js"></script>
463
+
393
464
  <!-- Tulip Project selector -->
394
- <script src="./../dist/timekit_project_selector.min.js"></script>
465
+ <script src="https://cdn.jsdelivr.net/npm/@tulipnpm/timekit_project_selector@latest/dist/timekit_project_selector.min.js"></script>
395
466
  </head>
396
467
 
397
468
  <body>
398
- <h1>Custom UI - TimeKit Project Selector Example</h1>
469
+ <h1>Custom UI - Tulip Appointments Web Widget Example</h1>
399
470
 
400
- <!-- Placeholder div for TK Projects of t_project_type = global_appointment_type_project -->
401
- <ul class="global_projects"></ul>
471
+ <!-- Placeholder div for TK Projects of t_project_type = service_project -->
472
+ <ul class="services"></ul>
402
473
 
403
474
  <!-- Placeholder div for TK Projects of t_project_type = store_project -->
404
475
  <ul class="stores"></ul>
@@ -407,86 +478,86 @@ timekit_project_selector.selectProject(timekitProject);
407
478
  <div id="bookingjs"></div>
408
479
 
409
480
  <script>
410
- $(document).ready(function () {
411
-
412
- const tps = timekit_project_selector;
413
-
414
- tps.init({
415
- // Insert widget key provided via TK admin panel
416
- app_key: '',
417
- // Set defaultUI to false as we are using a custom UI
418
- defaultUI: false,
419
- // Selector options still prvided but values are set to true
420
- selectorOptions: {
421
- // Step 1: Is to select the global_appointment_type_project
422
- global_appointment_type_project: true,
423
- // Step 2: Is to select the store project
424
- store_project: true,
425
- }
426
- }).then(() => {
427
- // You could use simple array instead of using the steps factor.
428
- // This factory makes it simple to track which TK project uuids were selected
429
- let stepsFactory = tps.getStepsFactory();
430
-
431
- let globalProjectFilters = [];
432
- // We only want to surface active global appointment types
433
- globalProjectFilters['t_disabled'] = 0;
434
- // We only want to surface public global appointment types
435
- globalProjectFilters['t_private'] = 0;
436
-
437
- // Fetch the global_appointment_type_project
438
- let globalProjects = tps.getStrategy().getProjects('global_appointment_type_project', globalProjectFilters);
439
-
440
- // Render out links for each global_appointment_type_project
441
- globalProjects.forEach((globalProject) => {
442
- let link = "<a href=\"#\" class=\"global_appointment\" id=\"" + globalProject.id + "\"> <li>'" + globalProject.name + "'</li></a>";
443
- $('.global_projects').append(link);
444
- })
481
+ const tps = timekit_project_selector;
482
+
483
+ tps.init({
484
+ // Insert widget key provided via TK admin panel
485
+ app_key: 'live_widget_key_xj4RMO93SIXSS9EbZiw286Fs0epKDnN6',
486
+ api_base_url: 'https://api-staging.timekit.io',
487
+ // If you need to filter by country
488
+ // region: 'USA',
489
+ // Set defaultUI to false as we are using a custom UI
490
+ defaultUI: false,
491
+ // Selector options still prvided but values are set to true
492
+ selectorOptions: {
493
+ // Step 1: Is to select the service_project
494
+ service_project: true,
495
+ // Step 2: Is to select the store project
496
+ store_project: true,
497
+ }
498
+ }).then(async () => {
499
+ let serviceHtml = '';
500
+ let locationHtml = '';
445
501
 
446
- // Register an event listener on the newly generated link
447
- $("a.global_appointment").click(function (event) {
448
- let storeProjectFilters = [];
449
- storeProjectFilters['t_global_appointment_type_project_uuid'] = $(this).attr('id');
450
- storeProjectFilters['t_disabled'] = 0;
451
- storeProjectFilters['t_private'] = 0;
502
+ // You could use simple array instead of using the steps factor.
503
+ // This factory makes it simple to track which TK project uuids were selected
504
+ let stepsFactory = tps.getStepsFactory();
505
+
506
+ // Fetch the service_project
507
+ let globalProjects = await tps.getStrategy('service_project').getProjects({ t_private: 0, t_disabled: 0 });
508
+
509
+ // Render out links for each service_project
510
+ globalProjects.forEach((globalProject) => {
511
+ serviceHtml += "<a href=\"#\" class=\"service\" id=\"" + globalProject.id + "\"> <li>" + globalProject.name + "</li></a>";
512
+ })
452
513
 
453
- // We add the UUID of the selected global_appointment_type_project. This will be used at the end
454
- // when we are selecting the store_appointment_type_project
455
- stepsFactory.addFilterForLastStep('t_global_appointment_type_project_uuid', $(this).attr('id'));
456
- stepsFactory.nextStep();
514
+ document.querySelector('.services').innerHTML = serviceHtml;
457
515
 
458
- let storeProjects = tps.getStrategy('store_project').getProjects('store_project', storeProjectFilters);
516
+ var services = document.querySelectorAll('a.service'), i;
517
+
518
+ for (i = 0; i < services.length; ++i) {
519
+ // Register an event listener on the newly generated link
520
+ services[i].addEventListener("click", async function (event) {
521
+
522
+ let service_project_id = event.target.parentNode.id;
523
+ let storeProjects = await tps.getStrategy('store_project').getProjects({
524
+ // t_private: 0, t_disabled: 0,
525
+ service_project_id: service_project_id
526
+ });
459
527
 
460
528
  // Render out links for each store_project
461
529
  storeProjects.forEach((project) => {
462
- let link = "<a href=\"#\" class=\"store_project\" id=\"" + project.id + "\"> <li>'" + project.name + "'</li></a>";
463
- $('.stores').append(link);
530
+ locationHtml += "<a href=\"#\" class=\"store_project\" id=\"" + project.id + "\"> <li>" + project.name + "</li></a>";
464
531
  })
465
532
 
466
- // Register an event listener on the newly generated store link
467
- $("a.store_project").click(function (event) {
533
+ document.querySelector('.stores').innerHTML = locationHtml;
468
534
 
469
- // We add the UUID of the selected store_project. This will be used at the end
470
- // when we are selecting the store_appointment_type_project
471
- stepsFactory.addFilterForLastStep('t_store_project_uuid', $(this).attr('id'));
472
- stepsFactory.nextStep();
535
+ var locations = document.querySelectorAll('a.store_project'), j;
473
536
 
474
- // From the step factory get the filters we had applied throughout the process
475
- let filters = stepsFactory.currentStep().getFilters();
476
- let store_appointments = tps.getStrategy().getProjects('store_appointment_type_project', filters);
537
+ for (j = 0; j < locations.length; ++j) {
538
+ // Register an event listener on the newly generated store link
539
+ locations[j].addEventListener("click", async function (e) {
540
+ let store_project_id = e.target.parentNode.id;
477
541
 
478
- // Render Booking.js
479
- tps.selectProject(store_appointments[0]);
480
- });
481
- });
542
+ const projects = await tps.getStrategy().getProjects({
543
+ store_project_id: store_project_id,
544
+ service_project_id: service_project_id
545
+ });
482
546
 
483
- }).catch((error) => {
484
- console.log(error.message);
485
- });
547
+ const selectedProject = projects.find(project => project.meta?.t_service_id === service_project_id);
548
+ // Render Booking.js
549
+ tps.selectProject(selectedProject);
550
+ });
551
+ }
552
+ });
553
+ }
486
554
 
555
+ }).catch((error) => {
556
+ console.log(error.message);
487
557
  });
488
558
  </script>
489
559
  </body>
490
560
 
491
561
  </html>
562
+
492
563
  ```