Add Activepieces integration for workflow automation
- Add Activepieces fork with SmoothSchedule custom piece - Create integrations app with Activepieces service layer - Add embed token endpoint for iframe integration - Create Automations page with embedded workflow builder - Add sidebar visibility fix for embed mode - Add list inactive customers endpoint to Public API - Include SmoothSchedule triggers: event created/updated/cancelled - Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
import { createAction } from '@activepieces/pieces-framework';
|
||||
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
|
||||
import { bookedinAuth } from '../../index';
|
||||
import { BASE_URL, getBookedinHeaders, leadIdsMultiSelectDropdown, extractApiKey } from '../common/props';
|
||||
|
||||
export const bulkDeleteLeads = createAction({
|
||||
name: 'bulkDeleteLeads',
|
||||
displayName: 'Bulk Delete Leads',
|
||||
description: 'Delete multiple leads (max 500 per request)',
|
||||
auth: bookedinAuth,
|
||||
props: {
|
||||
lead_ids: leadIdsMultiSelectDropdown,
|
||||
},
|
||||
async run({ auth, propsValue }) {
|
||||
const apiKey = extractApiKey(auth);
|
||||
const leadIds = Array.isArray(propsValue.lead_ids) ? propsValue.lead_ids : [propsValue.lead_ids];
|
||||
|
||||
if (leadIds.length === 0) {
|
||||
throw new Error('At least one lead must be selected');
|
||||
}
|
||||
|
||||
if (leadIds.length > 500) {
|
||||
throw new Error('Maximum 500 leads can be deleted per request');
|
||||
}
|
||||
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.POST,
|
||||
url: `${BASE_URL}/leads/bulk-delete`,
|
||||
headers: {
|
||||
...getBookedinHeaders(apiKey),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: {
|
||||
lead_ids: leadIds,
|
||||
},
|
||||
});
|
||||
|
||||
return response.body;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
|
||||
import { bookedinAuth } from '../../index';
|
||||
import { BASE_URL, getBookedinHeaders, extractApiKey } from '../common/props';
|
||||
|
||||
export const createLead = createAction({
|
||||
name: 'createLead',
|
||||
displayName: 'Create Lead',
|
||||
description: 'Creates a new lead in Bookedin AI',
|
||||
auth: bookedinAuth,
|
||||
props: {
|
||||
firstName: Property.ShortText({
|
||||
displayName: 'First Name',
|
||||
required: true,
|
||||
}),
|
||||
lastName: Property.ShortText({
|
||||
displayName: 'Last Name',
|
||||
required: true,
|
||||
}),
|
||||
email: Property.ShortText({
|
||||
displayName: 'Email',
|
||||
required: true,
|
||||
}),
|
||||
phone: Property.ShortText({
|
||||
displayName: 'Phone Number',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
async run({ auth, propsValue }) {
|
||||
const apiKey = extractApiKey(auth);
|
||||
|
||||
const payload = {
|
||||
contact: {
|
||||
name: {
|
||||
last: propsValue.lastName,
|
||||
first: propsValue.firstName,
|
||||
},
|
||||
email: propsValue.email,
|
||||
number: propsValue.phone,
|
||||
},
|
||||
};
|
||||
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.POST,
|
||||
url: `${BASE_URL}/leads/`,
|
||||
headers: {
|
||||
...getBookedinHeaders(apiKey),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: payload,
|
||||
});
|
||||
|
||||
return response.body;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
import { createAction } from '@activepieces/pieces-framework';
|
||||
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
|
||||
import { bookedinAuth } from '../../index';
|
||||
import { BASE_URL, getBookedinHeaders, leadIdDropdown, extractApiKey } from '../common/props';
|
||||
|
||||
export const deleteLead = createAction({
|
||||
name: 'deleteLead',
|
||||
displayName: 'Delete Lead',
|
||||
description: 'Delete a lead.',
|
||||
auth: bookedinAuth,
|
||||
props: {
|
||||
lead_id: leadIdDropdown,
|
||||
},
|
||||
async run({ auth, propsValue }) {
|
||||
const apiKey = extractApiKey(auth);
|
||||
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.DELETE,
|
||||
url: `${BASE_URL}/leads/${propsValue.lead_id}`,
|
||||
headers: getBookedinHeaders(apiKey),
|
||||
});
|
||||
|
||||
return response.body;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
import { createAction } from '@activepieces/pieces-framework';
|
||||
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
|
||||
import { bookedinAuth } from '../../index';
|
||||
import { BASE_URL, getBookedinHeaders, extractApiKey } from '../common/props';
|
||||
|
||||
export const getLeadStats = createAction({
|
||||
name: 'getLeadStats',
|
||||
displayName: 'Get Lead Stats',
|
||||
description: 'Get lead statistics (Hot, Warm, Cold, Objectives Met, Total).',
|
||||
auth: bookedinAuth,
|
||||
props: {},
|
||||
async run({ auth }) {
|
||||
const apiKey = extractApiKey(auth);
|
||||
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.GET,
|
||||
url: `${BASE_URL}/leads/stats`,
|
||||
headers: getBookedinHeaders(apiKey),
|
||||
});
|
||||
|
||||
return response.body;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
import { createAction } from '@activepieces/pieces-framework';
|
||||
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
|
||||
import { bookedinAuth } from '../../index';
|
||||
import { BASE_URL, getBookedinHeaders, leadIdDropdown, extractApiKey } from '../common/props';
|
||||
|
||||
export const getLead = createAction({
|
||||
name: 'getLead',
|
||||
displayName: 'Get Lead',
|
||||
description: 'Get a specific lead by ID.',
|
||||
auth: bookedinAuth,
|
||||
props: {
|
||||
lead_id: leadIdDropdown,
|
||||
},
|
||||
async run({ auth, propsValue }) {
|
||||
const apiKey = extractApiKey(auth);
|
||||
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.GET,
|
||||
url: `${BASE_URL}/leads/${propsValue.lead_id}`,
|
||||
headers: getBookedinHeaders(apiKey),
|
||||
});
|
||||
|
||||
return response.body;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
|
||||
import { bookedinAuth } from '../../index';
|
||||
import { BASE_URL, getBookedinHeaders, extractApiKey } from '../common/props';
|
||||
|
||||
export const getLeads = createAction({
|
||||
name: 'getLeads',
|
||||
displayName: 'Get Leads',
|
||||
description: 'Get all leads for the current business with pagination metadata.',
|
||||
auth: bookedinAuth,
|
||||
props: {
|
||||
search: Property.ShortText({
|
||||
displayName: 'Search',
|
||||
description: 'Search text in name, email, or phone number',
|
||||
required: false,
|
||||
}),
|
||||
source: Property.ShortText({
|
||||
displayName: 'Source',
|
||||
description: 'Filter by lead source (e.g., "API", "Import")',
|
||||
required: false,
|
||||
}),
|
||||
email: Property.ShortText({
|
||||
displayName: 'Email',
|
||||
description: 'Filter by exact email address',
|
||||
required: false,
|
||||
}),
|
||||
phone: Property.ShortText({
|
||||
displayName: 'Phone',
|
||||
description: 'Filter by phone number',
|
||||
required: false,
|
||||
}),
|
||||
limit: Property.Number({
|
||||
displayName: 'Limit',
|
||||
description: 'Number of leads to return',
|
||||
required: false,
|
||||
defaultValue: 100,
|
||||
}),
|
||||
skip: Property.Number({
|
||||
displayName: 'Skip',
|
||||
description: 'Number of leads to skip (pagination)',
|
||||
required: false,
|
||||
defaultValue: 0,
|
||||
}),
|
||||
},
|
||||
async run({ auth, propsValue }) {
|
||||
const apiKey = extractApiKey(auth);
|
||||
|
||||
const queryParams: Record<string, string> = {
|
||||
limit: (propsValue.limit ?? 100).toString(),
|
||||
skip: (propsValue.skip ?? 0).toString(),
|
||||
};
|
||||
|
||||
if (propsValue.search) queryParams['search'] = propsValue.search;
|
||||
if (propsValue.source) queryParams['source'] = propsValue.source;
|
||||
if (propsValue.email) queryParams['email'] = propsValue.email;
|
||||
if (propsValue.phone) queryParams['phone'] = propsValue.phone;
|
||||
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.GET,
|
||||
url: `${BASE_URL}/leads/`,
|
||||
headers: getBookedinHeaders(apiKey),
|
||||
queryParams,
|
||||
});
|
||||
|
||||
return response.body;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
|
||||
import { bookedinAuth } from '../../index';
|
||||
import { BASE_URL, getBookedinHeaders, leadIdDropdown, extractApiKey } from '../common/props';
|
||||
import { isNil } from '@activepieces/shared';
|
||||
|
||||
export const updateLead = createAction({
|
||||
name: 'updateLead',
|
||||
displayName: 'Update Lead',
|
||||
description: 'Update a lead.',
|
||||
auth: bookedinAuth,
|
||||
props: {
|
||||
lead_id: leadIdDropdown,
|
||||
firstName: Property.ShortText({
|
||||
displayName: 'First Name',
|
||||
required: false,
|
||||
}),
|
||||
lastName: Property.ShortText({
|
||||
displayName: 'Last Name',
|
||||
required: false,
|
||||
}),
|
||||
email: Property.ShortText({
|
||||
displayName: 'Email',
|
||||
required: false,
|
||||
}),
|
||||
phone: Property.ShortText({
|
||||
displayName: 'Phone Number',
|
||||
required: false,
|
||||
}),
|
||||
handling_status: Property.ShortText({
|
||||
displayName: 'Handling Status',
|
||||
required: false,
|
||||
}),
|
||||
update_json: Property.Json({
|
||||
displayName: 'Update Payload (JSON)',
|
||||
description: 'Optional JSON body for complex updates. Merges with individual fields above.',
|
||||
required: false,
|
||||
defaultValue: {},
|
||||
}),
|
||||
},
|
||||
async run({ auth, propsValue }) {
|
||||
const apiKey = extractApiKey(auth);
|
||||
const basePayload: Record<string, unknown> = {};
|
||||
|
||||
if (!isNil(propsValue.firstName) || !isNil(propsValue.lastName) || !isNil(propsValue.email) || !isNil(propsValue.phone)) {
|
||||
const contact: Record<string, unknown> = {};
|
||||
|
||||
const nameParts: Record<string, string> = {};
|
||||
if (!isNil(propsValue.firstName) && propsValue.firstName !== '') {
|
||||
nameParts['first'] = propsValue.firstName;
|
||||
}
|
||||
if (!isNil(propsValue.lastName) && propsValue.lastName !== '') {
|
||||
nameParts['last'] = propsValue.lastName;
|
||||
}
|
||||
if (Object.keys(nameParts).length > 0) {
|
||||
contact['name'] = nameParts;
|
||||
}
|
||||
|
||||
if (!isNil(propsValue.email) && propsValue.email !== '') {
|
||||
contact['email'] = propsValue.email;
|
||||
}
|
||||
if (!isNil(propsValue.phone) && propsValue.phone !== '') {
|
||||
contact['number'] = propsValue.phone;
|
||||
}
|
||||
|
||||
basePayload['contact'] = contact;
|
||||
}
|
||||
|
||||
if (!isNil(propsValue.handling_status) && propsValue.handling_status !== '') {
|
||||
basePayload['handling_status'] = propsValue.handling_status;
|
||||
}
|
||||
|
||||
const finalPayload = {
|
||||
...basePayload,
|
||||
...(propsValue.update_json || {}),
|
||||
};
|
||||
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.PUT,
|
||||
url: `${BASE_URL}/leads/${propsValue.lead_id}`,
|
||||
headers: {
|
||||
...getBookedinHeaders(apiKey),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: finalPayload,
|
||||
});
|
||||
|
||||
return response.body;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,138 @@
|
||||
import { Property } from '@activepieces/pieces-framework';
|
||||
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
|
||||
import { bookedinAuth } from '../../index';
|
||||
|
||||
export const BASE_URL = 'https://api.bookedin.ai/api/v1';
|
||||
|
||||
export const getBookedinHeaders = (apiKey: string) => {
|
||||
return {
|
||||
'X-API-Key': apiKey,
|
||||
'accept': 'application/json',
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
export const extractApiKey = (auth: unknown): string => {
|
||||
if (typeof auth === 'string') {
|
||||
return auth;
|
||||
}
|
||||
const authObj = auth as { secret_text?: string; auth?: string };
|
||||
return authObj?.secret_text || authObj?.auth || '';
|
||||
};
|
||||
|
||||
|
||||
const fetchLeadOptions = async (apiKey: string) => {
|
||||
const response = await httpClient.sendRequest<{
|
||||
items: Array<{
|
||||
id: string;
|
||||
contact: {
|
||||
name: {
|
||||
first: string;
|
||||
last: string;
|
||||
};
|
||||
email: string;
|
||||
};
|
||||
}>;
|
||||
}>({
|
||||
method: HttpMethod.GET,
|
||||
url: `${BASE_URL}/leads/`,
|
||||
headers: getBookedinHeaders(apiKey),
|
||||
queryParams: {
|
||||
limit: '100',
|
||||
skip: '0',
|
||||
},
|
||||
});
|
||||
|
||||
return response.body.items.map((lead) => {
|
||||
const firstName = lead.contact?.name?.first || '';
|
||||
const lastName = lead.contact?.name?.last || '';
|
||||
const email = lead.contact?.email || '';
|
||||
const name = [firstName, lastName].filter(Boolean).join(' ').trim();
|
||||
const label = name ? `${name} (${email})` : email || lead.id;
|
||||
|
||||
return {
|
||||
label,
|
||||
value: lead.id,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const leadIdDropdown = Property.Dropdown({
|
||||
auth: bookedinAuth,
|
||||
displayName: 'Lead',
|
||||
description: 'Select a lead',
|
||||
required: true,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Please connect your account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const apiKey = extractApiKey(auth);
|
||||
if (!apiKey) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'API key is missing',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
const options = await fetchLeadOptions(apiKey);
|
||||
return {
|
||||
disabled: false,
|
||||
options,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Failed to load leads. Please check your connection.',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const leadIdsMultiSelectDropdown = Property.MultiSelectDropdown({
|
||||
auth: bookedinAuth,
|
||||
displayName: 'Leads',
|
||||
description: 'Select leads to delete (max 500)',
|
||||
required: true,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Please connect your account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const apiKey = extractApiKey(auth);
|
||||
if (!apiKey) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'API key is missing',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
const options = await fetchLeadOptions(apiKey);
|
||||
return {
|
||||
disabled: false,
|
||||
options,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Failed to load leads. Please check your connection.',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user