
Research
/Security News
Critical Vulnerability in NestJS Devtools: Localhost RCE via Sandbox Escape
A flawed sandbox in @nestjs/devtools-integration lets attackers run code on your machine via CSRF, leading to full Remote Code Execution (RCE).
@tulipnpm/timekit_project_selector
Advanced tools
The Tulip Appointments Web Widget is a library that extends the functionality of TimeKit's Booking Widget. 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.
<link rel="stylesheet" href="https://cdn.timekit.io/booking-js/v3/booking.min.css" />
<script type="text/javascript" src="//cdn.timekit.io/booking-js/v3/booking.min.js" defer></script>
<script src="https://cdn.jsdelivr.net/npm/@tulipnpm/timekit_project_selector@latest/dist/timekit_project_selector.min.js"></script>
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:
timekit_project_selector.init({
app_key: <timekit_app_key>,
api_base_url: <timekit_api_url>
}).then(() => {
// Your code here...
});
This will connect to TimeKit's API and load in all the required data. Once the initialization is completed, the exposed methods will be able to be used. If enabled, it will also render the default UI.
Tulip Appointments Web Widget has many configuration options:
Option | Optional? | Default value | Description |
---|---|---|---|
app_key | No | Token required to connect to TimeKit API. Can be found at https://admin.timekit.io/a/apps/<app_slug>/apisettings/keys | |
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 . |
defaultUI | Yes | true | When true, will create the default user interface for the project selector. See Widget Modes below |
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 below |
includePrivateAppointments | Yes | false | When true, private appointment types will be fetched from the TimeKit API |
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 | |
selectorOptions | No | See below for details | |
widgetImageUrl | Yes | Tulip Appointments Icon | When using the default widget UI, change this value to your desired image URL to replace widget image |
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 |
shouldConsiderAssociateAvailability | Yes | false | When enabled, this configuration ensures that service availability considers store associate schedules, returning only times when associates are available. |
shouldAutomaticallyBookAssociates | Yes | false | When enabled, associates are automatically assigned to bookings. |
There are three modes of UI the Tulip Appointments Web Widget:
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
.
embed=true | embed=false | |
---|---|---|
defaultUI=true | Page Mode | Popup Mode |
defaultUI=false | Custom UI | Custom UI |
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
.
NOTE: If you are using the widget view, you cannot have a div with ID timekit-project-selector-container on your DOM.
timekit_project_selector.init({
app_key: <timekit_app_key>,
defaultUI: true,
});
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.
<div id="timekit-project-selector-container"></div>
<script>
timekit_project_selector.init({
app_key: <timekit_app_key>,
defaultUI: true,
embed: true,
});
</script>
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
.
For a more in-depth example, see Example Custom UI
<!-- Container div for the calendar to display-->
<div id="bookingjs"></div>
<script>
timekit_project_selector.init({
app_key: <timekit_app_key>,
defaultUI: false,
});
</script>
The selector options are how you can set the metadata fields that you want the user to select from. The order that the keys are placed in the object is the order the customer will see the selector options. If you are using the default UI, you will also need to specify the selector option copyright fields for each option. If you are not using the default UI, simply assign the keys to have value true
.
// Example without using default UI
timekit_project_selector.init({
...,
selectorOptions: {
store_project: true,
service_project: true,
}
});
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
When using the default UI, you must set the selector display values for each selector option. The copyright is very flexible to allow you to display the data you want on each option. The title and description fields are what we show above the card selectors. For inside the cards, you can customize the card_title, card_body, card_footer. You are also able to supply an optional card_image which will display an image inside the card.
Please note that the description field can only be up to 300 characters long before it gets cut off from the default UI. This is to prevent the header from becoming too large.
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)
strategy: 'store_project',
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.
To enable this search bar, you can include the following inside of a Selector Option.
search_bar: {
enabled: true,
placeholder: 'Placeholder text inside of the search bar'
}
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.
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.
Note that the search will only happen after at least 3 or more characters are entered.
There are many different ways to display values to the UI using the selector options.
For each projects you can apply default filters by meta data. You can add many filters by key value and only projects with default filters will be displayed
filters: {
't_disabled': 0,
't_private': 0
}
Geo search bar is an alternative to the selector option bar and they shouldn't be used together. When this option is specified, the user will be asked for their geolocation in the browser. If the user shares it, the list of the 5 closest stores will be displayed.
geo_search_bar: {
placeholder: 'Search for a city or postal code'
}
Adding a string such as 'Select a Store' will display that hardcoded string in the place of the placeholder. This can also be used to display custom HTML elements like icons in the UI.
Adding a string with the [meta] prefix will display the metadata for the selected option's project. For example, if your project has metadata of meta.address = '123 Main Street'
, when you use [meta]address
, the UI will deploy '123 Main Street'.
Adding a string with the [project] prefix will have the same effect as the meta prefix, but allows you to get any value from the TimeKit Project. Refer to TimeKit Project Docs for available fields.
To combine strings with replacement values, wrap the replacement values with curly braces and write the string how you want it to be displayed. For example to add the address and city of a project, you can add the following: 'The address of the store is {{[meta]address}} in {{meta]city}}'.
Here is a sample of combined selectorOptions.
timekit_project_selector.init({
...,
selectorOptions: {
service_project: {
strategy: 'service_project',
card_title: `{{[project]name}}`,
title: 'Select an Appointment Type',
card_image: `{{[project]image_url}}`,
card_body: `{{[project]description}}`,
description: 'Which appointment type would you like?',
card_footer: '<i class="far fa-clock"></i> {{[project]availability.length}}',
filters: {
't_disabled': 0,
't_private': 0
}
},
store_project: {
title: 'Select a Store',
strategy: 'store_project',
card_title: '[meta]t_store_name',
card_footer: '[meta]t_store_phone',
card_body: `{{[meta]t_store_address}}, {{[meta]t_store_city}}`,
description: 'Choose a store from the following that you will be visiting for your appointment.',
search_bar: {
enabled: true,
placeholder: 'Search for a city or postal code'
},
},
}
});
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
.
timekit_project_selector.init({
app_key: <timekit_app_key>,
...,
includePrivateAppointments: true,
});
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:
timekit_project_selector.init({
app_key: <timekit_app_key>,
defaultUI: false,
includePrivateAppointments: true,
selectorOptions: {
service_project: true,
store_project: true,
}
}).then(() => {
// Add this filter whenever you want to surface only public appointment types
globalProjectFilters['t_private'] = 0;
});
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
.
timekit_project_selector.init({
app_key: <timekit_app_key>,
...,
shouldConsiderAssociateAvailability: true,
});
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
.
timekit_project_selector.init({
app_key: <timekit_app_key>,
...,
shouldAutomaticallyBookAssociates: true,
});
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 in Timekit under API Settings with the following url: https://your_server_url/api/customers/timekit_webhook_connect_client.
timekit_project_selector.init({
app_key: <timekit_app_key>,
...,
duplicateCustomerCheck: true,
});
More information about Timekit's webhooks can be found here
See below for how to use the exposed methods.
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.
Asynchronous method that initiate the Tulip Appointments Web Widget.
timekit_project_selector.init({
app_key: <timekit_app_key>,
}).then(() => {
// Enter code here ...
});
Returns a strategy for searching TimeKit projects. Optionally takes a strategy type argument for use custom strategy instead default.
timekit_project_selector.getStrategy();
timekit_project_selector.getStrategy('store_project');
Returns a list of TimeKit projects Optionally takes project type for return projects by project type only.(Filters not using if null) Optionally takes filters for return filtered projects
await timekit_project_selector.getStrategy('store_project').getProjects({ id: 'ef0cee8a-6096-453f-a634-1597b359cdfa'});
Returns an object of metadata key to selected value that represents the filter to value applied to the project selector. Optionally takes a key argument.
timekit_project_selector.getFilters();
timekit_project_selector.getFilters('appointment_type');
Adds a key-value pair to the filter manager.
timekit_project_selector.addFilter('appointment-type', 'fitting');
Removes a key-value pair from the filter manager. Takes the key of the filter as an argument.
timekit_project_selector.removeFilter('appointment-type');
Opens the BookingJS widget for the selected project. Takes a TimeKit Project as an argument. See BookingJS Documentation for additional details.
timekit_project_selector.selectProject(timekitProject);
selectorOptions: {
service_project: {
strategy: 'service_project',
card_title: `{{[project]name}}`,
title: 'Select an Appointment Type',
card_image: `{{[project]image_url}}`,
card_body: `{{[project]description}}`,
description: 'Which appointment type would you like?',
card_footer: '<i class="far fa-clock"></i> {{[project]availability.length}}',
filters: {
't_disabled': 0,
't_private': 0
}
},
store_project: {
title: 'Select a Store',
strategy: 'store_project',
card_title: '[meta]t_store_name',
card_footer: '[meta]t_store_phone',
card_body: `{{[meta]t_store_address}}, {{[meta]t_store_city}}`,
description: 'Choose a store from the following that you will be visiting for your appointment.',
search_bar: {
enabled: true,
placeholder: 'Search for a city or postal code'
},
}
}
selectorOptions: {
store_project: {
title: 'Select a Store',
strategy: 'store_project',
card_title: '[meta]t_store_name',
card_footer: '[meta]t_store_phone',
card_body: `{{[meta]t_store_address}}, {{[meta]t_store_city}}`,
description: 'Choose a store from the following that you will be visiting for your appointment.',
search_bar: {
enabled: true,
placeholder: 'Search for a city or postal code'
},
//strategy: 'store_project'
},
store_appointment_type_project: {
strategy: 'service_project',
card_title: `{{[project]name}}`,
title: 'Select an Appointment Type',
card_image: `{{[project]image_url}}`,
card_body: `{{[project]description}}`,
description: 'Which appointment type would you like?',
card_footer: '<i class="far fa-clock"></i> {{[project]availability.length}}',
filters: {
't_disabled': 0,
't_private': 0
}
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Custom UI - Tulip Appointments Web Widget Example</title>
<!-- Booking.js minified dependency -->
<link rel="stylesheet" href="https://cdn.timekit.io/booking-js/v3/booking.min.css" />
<script type="text/javascript" src="https://cdn.timekit.io/booking-js/v3/booking.min.js"></script>
<!-- Tulip Project selector -->
<script src="https://cdn.jsdelivr.net/npm/@tulipnpm/timekit_project_selector@latest/dist/timekit_project_selector.min.js"></script>
</head>
<body>
<h1>Custom UI - Tulip Appointments Web Widget Example</h1>
<!-- Placeholder div for TK Projects of t_project_type = service_project -->
<ul class="services"></ul>
<!-- Placeholder div for TK Projects of t_project_type = store_project -->
<ul class="stores"></ul>
<!-- Placeholder div for bookingjs to be injected into -->
<div id="bookingjs"></div>
<script>
const tps = timekit_project_selector;
tps.init({
// Insert widget key provided via TK admin panel
app_key: 'live_widget_key_xj4RMO93SIXSS9EbZiw286Fs0epKDnN6',
api_base_url: 'https://api-staging.timekit.io',
// If you need to filter by country
// region: 'USA',
// Set defaultUI to false as we are using a custom UI
defaultUI: false,
// Selector options still prvided but values are set to true
selectorOptions: {
// Step 1: Is to select the service_project
service_project: true,
// Step 2: Is to select the store project
store_project: true,
}
}).then(async () => {
let serviceHtml = '';
let locationHtml = '';
// You could use simple array instead of using the steps factor.
// This factory makes it simple to track which TK project uuids were selected
let stepsFactory = tps.getStepsFactory();
// Fetch the service_project
let globalProjects = await tps.getStrategy('service_project').getProjects({ t_private: 0, t_disabled: 0 });
// Render out links for each service_project
globalProjects.forEach((globalProject) => {
serviceHtml += "<a href=\"#\" class=\"service\" id=\"" + globalProject.id + "\"> <li>" + globalProject.name + "</li></a>";
})
document.querySelector('.services').innerHTML = serviceHtml;
var services = document.querySelectorAll('a.service'), i;
for (i = 0; i < services.length; ++i) {
// Register an event listener on the newly generated link
services[i].addEventListener("click", async function (event) {
let service_project_id = event.target.parentNode.id;
let storeProjects = await tps.getStrategy('store_project').getProjects({
// t_private: 0, t_disabled: 0,
service_project_id: service_project_id
});
// Render out links for each store_project
storeProjects.forEach((project) => {
locationHtml += "<a href=\"#\" class=\"store_project\" id=\"" + project.id + "\"> <li>" + project.name + "</li></a>";
})
document.querySelector('.stores').innerHTML = locationHtml;
var locations = document.querySelectorAll('a.store_project'), j;
for (j = 0; j < locations.length; ++j) {
// Register an event listener on the newly generated store link
locations[j].addEventListener("click", async function (e) {
let store_project_id = e.target.parentNode.id;
const projects = await tps.getStrategy().getProjects({
store_project_id: store_project_id,
service_project_id: service_project_id
});
const selectedProject = projects.find(project => project.meta?.t_service_id === service_project_id);
// Render Booking.js
tps.selectProject(selectedProject);
});
}
});
}
}).catch((error) => {
console.log(error.message);
});
</script>
</body>
</html>
FAQs
Tulip Appointments Web Widget
The npm package @tulipnpm/timekit_project_selector receives a total of 135 weekly downloads. As such, @tulipnpm/timekit_project_selector popularity was classified as not popular.
We found that @tulipnpm/timekit_project_selector demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
/Security News
A flawed sandbox in @nestjs/devtools-integration lets attackers run code on your machine via CSRF, leading to full Remote Code Execution (RCE).
Product
Customize license detection with Socket’s new license overlays: gain control, reduce noise, and handle edge cases with precision.
Product
Socket now supports Rust and Cargo, offering package search for all users and experimental SBOM generation for enterprise projects.