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:
poduck
2025-12-18 22:59:37 -05:00
parent 9848268d34
commit 3aa7199503
16292 changed files with 1284892 additions and 4708 deletions

View File

@@ -0,0 +1,62 @@
import { createAction, DynamicPropsValue, Property } from '@activepieces/pieces-framework';
import { webflowAuth } from '../..';
import { webflowProps } from '../common/props';
import { WebflowApiClient } from '../common/client';
export const webflowCreateCollectionItemAction = createAction({
auth: webflowAuth,
name: 'create_collection_item',
displayName: 'Create Collection Item',
description: 'Creates new collection item.',
props: {
site_id: webflowProps.site_id,
collection_id: webflowProps.collection_id,
collection_fields: webflowProps.collection_fields,
is_archived: Property.Checkbox({
displayName: 'Is Archived',
description: 'Whether the item is archived or not',
required: false,
}),
is_draft: Property.Checkbox({
displayName: 'Is Draft',
description: 'Whether the item is a draft or not',
required: false,
}),
},
async run(context) {
const collectionId = context.propsValue.collection_id;
const isArchived = context.propsValue.is_archived;
const isDraft = context.propsValue.is_draft;
const collectionInputFields = context.propsValue.collection_fields;
const client = new WebflowApiClient(context.auth.access_token);
const { fields: CollectionFields } = await client.getCollection(collectionId);
const formattedCollectionFields: DynamicPropsValue = {};
for (const field of CollectionFields) {
const fieldValue = collectionInputFields[field.slug];
if (fieldValue !== undefined && fieldValue !== '') {
switch (field.type) {
case 'ImageRef':
case 'FileRef':
formattedCollectionFields[field.slug] = { url: fieldValue };
break;
case 'Set':
formattedCollectionFields[field.slug] = fieldValue.map((url: string) => ({ url: url }));
break;
case 'Number':
formattedCollectionFields[field.slug] = Number(fieldValue);
break;
default:
formattedCollectionFields[field.slug] = fieldValue;
}
}
}
return await client.createCollectionItem(collectionId, {
fields: { ...formattedCollectionFields, _archived: isArchived, _draft: isDraft },
});
},
});

View File

@@ -0,0 +1,26 @@
import { createAction } from '@activepieces/pieces-framework';
import { webflowAuth } from '../..';
import { webflowProps } from '../common/props';
import { WebflowApiClient } from '../common/client';
export const webflowDeleteCollectionItem = createAction({
auth: webflowAuth,
name: 'delete_collection_item',
description: 'Delete collection item',
displayName: 'Delete an item in a collection',
props: {
site_id: webflowProps.site_id,
collection_id: webflowProps.collection_id,
collection_item_id: webflowProps.collection_item_id,
},
async run(context) {
const collectionId = context.propsValue.collection_id;
const collectionItemId = context.propsValue.collection_item_id;
const client = new WebflowApiClient(context.auth.access_token);
return await client.deleteCollectionItem(collectionId, collectionItemId);
},
});

View File

@@ -0,0 +1,70 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import {
HttpRequest,
HttpMethod,
httpClient,
AuthenticationType,
} from '@activepieces/pieces-common';
import { webflowAuth } from '../..';
import { webflowProps } from '../common/props';
export const webflowFindCollectionItem = createAction({
auth: webflowAuth,
name: 'find_collection_item',
description: 'Find collection item in a collection by field',
displayName: 'Find a Collection Item by Field',
props: {
site_id: webflowProps.site_id,
collection_id: webflowProps.collection_id,
field_name: Property.ShortText({
displayName: 'Field Name',
description: 'The name of the field to search by',
required: true,
}),
field_value: Property.ShortText({
displayName: 'Field Value',
description: 'The value of the field to search for',
required: true,
}),
max_results: Property.Number({
displayName: 'Max Results',
description: 'The maximum number of results to return',
required: false,
}),
},
async run(configValue) {
const accessToken = configValue.auth['access_token'];
const collectionId = configValue.propsValue['collection_id'];
const fieldName = configValue.propsValue['field_name'];
const fieldValue = configValue.propsValue['field_value'];
const maxResults = configValue.propsValue['max_results'];
const request: HttpRequest = {
method: HttpMethod.GET,
url: `https://api.webflow.com/collections/${collectionId}/items`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: accessToken,
},
};
try {
const res = await httpClient.sendRequest(request);
if (res.status !== 200) {
throw new Error('Failed to fetch collection items');
}
const items = res.body.items;
const matches = items
.filter((item: any) => {
return item.fields[fieldName] === fieldValue;
})
.slice(0, maxResults);
return { success: true, result: matches };
} catch (err) {
return { success: false, message: err };
}
},
});

View File

@@ -0,0 +1,25 @@
import { createAction } from '@activepieces/pieces-framework';
import { webflowAuth } from '../..';
import { webflowProps } from '../common/props';
import { WebflowApiClient } from '../common/client';
export const webflowFindOrder = createAction({
auth: webflowAuth,
name: 'find_order',
description: 'Find order',
displayName: 'Find an order',
props: {
site_id: webflowProps.site_id,
order_id: webflowProps.order_id,
},
async run(context) {
const orderId = context.propsValue.order_id;
const siteId = context.propsValue.site_id;
const client = new WebflowApiClient(context.auth.access_token);
return await client.getOrder(siteId, orderId);
},
});

View File

@@ -0,0 +1,31 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { webflowAuth } from '../..';
import { webflowProps } from '../common/props';
import { WebflowApiClient } from '../common/client';
export const webflowFulfillOrder = createAction({
auth: webflowAuth,
name: 'fulfill_order',
description: 'Fulfill order',
displayName: 'Fulfill an order',
props: {
site_id: webflowProps.site_id,
order_id: webflowProps.order_id,
send_order_fulfilled_email: Property.Checkbox({
displayName: 'Send Order Fulfilled Email',
description: 'Send an email to the customer that their order has been fulfilled',
required: false,
}),
},
async run(context) {
const orderId = context.propsValue.order_id;
const siteId = context.propsValue.site_id;
const sendOrderFulfilledEmail = context.propsValue.send_order_fulfilled_email;
const client = new WebflowApiClient(context.auth.access_token);
return await client.fulfillOrder(siteId, orderId, { sendOrderFulfilledEmail });
},
});

View File

@@ -0,0 +1,26 @@
import { createAction } from '@activepieces/pieces-framework';
import { webflowAuth } from '../..';
import { webflowProps } from '../common/props';
import { WebflowApiClient } from '../common/client';
export const webflowGetCollectionItem = createAction({
auth: webflowAuth,
name: 'get_collection_item',
description: 'Get collection item in a collection by ID',
displayName: 'Get a Collection Item by ID',
props: {
site_id: webflowProps.site_id,
collection_id: webflowProps.collection_id,
collection_item_id: webflowProps.collection_item_id,
},
async run(context) {
const collectionId = context.propsValue.collection_id;
const collectionItemId = context.propsValue.collection_item_id;
const client = new WebflowApiClient(context.auth.access_token);
return await client.getCollectionItem(collectionId, collectionItemId);
},
});

View File

@@ -0,0 +1,26 @@
import { createAction } from '@activepieces/pieces-framework';
import { webflowAuth } from '../..';
import { webflowProps } from '../common/props';
import { WebflowApiClient } from '../common/client';
export const webflowPublishCollectionItem = createAction({
auth: webflowAuth,
name: 'publish_collection_item',
description: 'Publish collection item',
displayName: 'Publish a Collection Item',
props: {
site_id: webflowProps.site_id,
collection_id: webflowProps.collection_id,
collection_item_id: webflowProps.collection_item_id,
},
async run(context) {
const collectionId = context.propsValue.collection_id;
const collectionItemId = context.propsValue.collection_item_id;
const client = new WebflowApiClient(context.auth.access_token);
return await client.publishCollectionItem(collectionId, collectionItemId);
},
});

View File

@@ -0,0 +1,47 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { webflowAuth } from '../..';
import { webflowProps } from '../common/props';
import { WebflowApiClient } from '../common/client';
export const webflowRefundOrder = createAction({
auth: webflowAuth,
name: 'refund_order',
description: 'Refund order',
displayName: 'Refund an order',
props: {
site_id: webflowProps.site_id,
order_id: webflowProps.order_id,
// reason: Property.StaticDropdown({
// displayName: 'Reason',
// description: 'The reason for the refund',
// required: false,
// options: {
// disabled: false,
// options: [
// {
// label: 'Duplicate',
// value: 'duplicate',
// },
// {
// label: 'Fraudulent',
// value: 'fraudulent',
// },
// {
// label: 'Requested',
// value: 'requested',
// },
// ],
// },
// }),
},
async run(context) {
const orderId = context.propsValue.order_id;
const siteId = context.propsValue.site_id;
const client = new WebflowApiClient(context.auth.access_token);
return await client.refundOrder(siteId, orderId);
},
});

View File

@@ -0,0 +1,25 @@
import { createAction } from '@activepieces/pieces-framework';
import { webflowAuth } from '../..';
import { webflowProps } from '../common/props';
import { WebflowApiClient } from '../common/client';
export const webflowUnfulfillOrder = createAction({
auth: webflowAuth,
name: 'unfulfill_order',
description: 'Unfulfill order',
displayName: 'Unfulfill an order',
props: {
site_id: webflowProps.site_id,
order_id: webflowProps.order_id,
},
async run(context) {
const orderId = context.propsValue.order_id;
const siteId = context.propsValue.site_id;
const client = new WebflowApiClient(context.auth.access_token);
return await client.unfulfillOrder(siteId, orderId);
},
});

View File

@@ -0,0 +1,74 @@
import { createAction, DynamicPropsValue, Property } from '@activepieces/pieces-framework';
import { webflowAuth } from '../..';
import { webflowProps } from '../common/props';
import { WebflowApiClient } from '../common/client';
export const webflowUpdateCollectionItem = createAction({
auth: webflowAuth,
name: 'update_collection_item',
description: 'Update collection item',
displayName: 'Update an item in a collection',
props: {
site_id: webflowProps.site_id,
collection_id: webflowProps.collection_id,
collection_item_id: webflowProps.collection_item_id,
collection_fields: webflowProps.collection_fields,
is_archived: Property.Checkbox({
displayName: 'Is Archived',
description: 'Whether the item is archived or not',
required: false,
}),
is_draft: Property.Checkbox({
displayName: 'Is Draft',
description: 'Whether the item is a draft or not',
required: false,
}),
},
async run(context) {
const collectionId = context.propsValue.collection_id;
const collectionItemId = context.propsValue.collection_item_id;
const isArchived = context.propsValue.is_archived;
const isDraft = context.propsValue.is_draft;
const collectionInputFields = context.propsValue.collection_fields;
const client = new WebflowApiClient(context.auth.access_token);
const { fields: CollectionFields } = await client.getCollection(collectionId);
const formattedCollectionFields: DynamicPropsValue = {};
for (const field of CollectionFields) {
const fieldValue = collectionInputFields[field.slug];
if (fieldValue !== undefined && fieldValue !== '') {
switch (field.type) {
case 'ImageRef':
case 'FileRef':
formattedCollectionFields[field.slug] = { url: fieldValue };
break;
case 'Set':
if (fieldValue.length > 0) {
formattedCollectionFields[field.slug] = fieldValue.map((url: string) => ({
url: url,
}));
}
break;
case 'ItemRefSet':
if (fieldValue.length > 0) {
formattedCollectionFields[field.slug] = fieldValue;
}
break;
case 'Number':
formattedCollectionFields[field.slug] = Number(fieldValue);
break;
default:
formattedCollectionFields[field.slug] = fieldValue;
}
}
}
return await client.updateCollectionItem(collectionId, collectionItemId, {
fields: { ...formattedCollectionFields, _archived: isArchived, _draft: isDraft },
});
},
});