@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 +210 -139
- package/dist/timekit_project_selector.js +4848 -694
- package/dist/timekit_project_selector.min.js +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Tulip Appointments Web Widget
|
|
2
2
|
|
|
3
|
-
The
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
|
37
|
-
|
|
|
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
|
+

|
|
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
|
+

|
|
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
|
-
|
|
128
|
+
service_project: true,
|
|
55
129
|
}
|
|
56
130
|
});
|
|
57
131
|
```
|
|
58
|
-
> You must use project type as key in the selectorOptions. Ex.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
186
|
-
|
|
248
|
+
...,
|
|
249
|
+
includePrivateAppointments: true,
|
|
187
250
|
});
|
|
188
251
|
```
|
|
189
252
|
|
|
190
|
-
|
|
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
|
-
###
|
|
270
|
+
### Include Associate Schedules In Project Availability (Optional)
|
|
202
271
|
|
|
203
|
-
By default widget
|
|
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
|
-
|
|
278
|
+
shouldConsiderAssociateAvailability: true,
|
|
210
279
|
});
|
|
211
280
|
```
|
|
212
281
|
|
|
213
|
-
|
|
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
|
-
|
|
219
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 -
|
|
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="
|
|
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="
|
|
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 -
|
|
469
|
+
<h1>Custom UI - Tulip Appointments Web Widget Example</h1>
|
|
399
470
|
|
|
400
|
-
<!-- Placeholder div for TK Projects of t_project_type =
|
|
401
|
-
<ul class="
|
|
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
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
463
|
-
$('.stores').append(link);
|
|
530
|
+
locationHtml += "<a href=\"#\" class=\"store_project\" id=\"" + project.id + "\"> <li>" + project.name + "</li></a>";
|
|
464
531
|
})
|
|
465
532
|
|
|
466
|
-
|
|
467
|
-
$("a.store_project").click(function (event) {
|
|
533
|
+
document.querySelector('.stores').innerHTML = locationHtml;
|
|
468
534
|
|
|
469
|
-
|
|
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
|
-
|
|
475
|
-
|
|
476
|
-
|
|
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
|
-
|
|
479
|
-
|
|
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
|
-
|
|
484
|
-
|
|
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
|
```
|