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,65 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { lightfunnelsAuth } from '../../index';
import { lightfunnelsCommon } from '../common/index';
export const cancelOrder = createAction({
auth: lightfunnelsAuth,
name: 'cancel_order',
displayName: 'Cancel Order',
description: 'Cancel an order',
props: {
orderId: Property.ShortText({
displayName: 'Order ID',
description: 'The ID of the order to cancel',
required: true,
}),
reason: Property.ShortText({
displayName: 'Reason',
description: 'Reason for cancellation',
required: true,
}),
notifyCustomer: Property.Checkbox({
displayName: 'Notify Customer',
description: 'Whether to notify the customer',
required: false,
defaultValue: true,
}),
refund: Property.Checkbox({
displayName: 'Refund',
description: 'Whether to refund the order',
required: false,
defaultValue: false,
}),
},
async run(context) {
const { orderId, reason, notifyCustomer, refund } = context.propsValue;
const graphqlQuery = `
mutation cancelOrderMutation($id: ID!, $reason: String!, $notifyCustomer: Boolean!, $refund: Boolean!) {
cancelOrder(id: $id, reason: $reason, notifyCustomer: $notifyCustomer, refund: $refund) {
id
_id
name
cancelled_at
financial_status
fulfillment_status
}
}
`;
const variables = {
id: orderId,
reason,
notifyCustomer: notifyCustomer ?? true,
refund: refund ?? false,
};
const response = await lightfunnelsCommon.makeGraphQLRequest(
context.auth,
graphqlQuery,
variables
);
return response.data.cancelOrder;
},
});

View File

@@ -0,0 +1,90 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { lightfunnelsAuth } from '../../index';
import { lightfunnelsCommon } from '../common/index';
export const createCustomer = createAction({
auth: lightfunnelsAuth,
name: 'create_customer',
displayName: 'Create Customer',
description: 'Create a new customer',
props: {
firstName: Property.ShortText({
displayName: 'First Name',
description: 'First name of the customer',
required: true,
}),
lastName: Property.ShortText({
displayName: 'Last Name',
description: 'Last name of the customer',
required: true,
}),
email: Property.ShortText({
displayName: 'Email',
description: 'Email of the customer',
required: true,
}),
phone: Property.ShortText({
displayName: 'Phone',
description: 'Phone number of the customer eg: +1234567890',
required: true,
}),
acceptsMarketing: Property.Checkbox({
displayName: 'Accepts Marketing',
description: 'Whether the customer accepts marketing communications',
required: true,
}),
tags: Property.Array({
displayName: 'Tags',
description: 'Tags to associate with the customer',
required: true,
properties: {
tag: Property.ShortText({
displayName: 'Tag',
required: true,
}),
},
}),
},
async run(context) {
const { firstName, lastName, email, phone, acceptsMarketing, tags } = context.propsValue;
const graphqlQuery = `
mutation createCustomerMutation($node: InputCustomer!) {
createCustomer(node: $node) {
id
_id
first_name
last_name
full_name
email
phone
accepts_marketing
tags
created_at
updated_at
}
}
`;
const tagArray = (tags || []).map((t: any) => t.tag);
const variables = {
node: {
first_name: firstName,
last_name: lastName,
email,
phone,
accepts_marketing: acceptsMarketing,
tags: tagArray,
},
};
const response = await lightfunnelsCommon.makeGraphQLRequest(
context.auth,
graphqlQuery,
variables
);
return response.data.createCustomer;
},
});

View File

@@ -0,0 +1,184 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { lightfunnelsAuth } from '../../index';
import { lightfunnelsCommon } from '../common/index';
export const createProduct = createAction({
auth: lightfunnelsAuth,
name: 'create_product',
displayName: 'Create Product',
description: 'Create a new product',
props: {
title: Property.ShortText({
displayName: 'Title',
description: 'The title of the product',
required: true,
}),
price: Property.Number({
displayName: 'Price',
description: 'The price of the product',
required: true,
}),
description: Property.LongText({
displayName: 'Description',
description: 'The description of the product',
required: false,
}),
compare_at_price: Property.Number({
displayName: 'Compare at Price',
description: 'The original price before discount',
required: false,
}),
sku: Property.ShortText({
displayName: 'SKU',
description: 'The SKU of the product',
required: false,
}),
options: Property.Array({
displayName: 'Options',
description: 'Product options like size, color, etc.',
required: true,
properties: {
id: Property.ShortText({
displayName: 'Option ID',
description: 'Unique identifier for the option',
required: true,
}),
label: Property.ShortText({
displayName: 'Label',
description: 'The option label (e.g., "Color", "Size")',
required: false,
}),
options: Property.Json({
displayName: 'Option Values',
description: 'Available values for this option (e.g., [{"value": "Red"}, {"value": "Blue"}])',
required: true,
}),
type: Property.StaticDropdown({
displayName: 'Type',
description: 'The type of option',
required: false,
options: {
disabled: false,
options: [
{ label: 'Text', value: 'text' },
{ label: 'Color', value: 'color' },
{ label: 'Image', value: 'image' },
],
},
}),
},
}),
variants: Property.Array({
displayName: 'Variants',
description: 'Product variants with their details',
required: true,
properties: {
id: Property.ShortText({
displayName: 'Variant ID',
description: 'Unique identifier for the variant',
required: true,
}),
sku: Property.ShortText({
displayName: 'Variant SKU',
description: 'SKU for this specific variant',
required: false,
}),
price: Property.Number({
displayName: 'Variant Price',
description: 'Price for this variant',
required: false,
}),
compare_at_price: Property.Number({
displayName: 'Variant Compare at Price',
description: 'Original price for this variant',
required: false,
}),
options: Property.Json({
displayName: 'Variant Options',
description: 'Option values for this variant (array of objects with id and value pairs)',
required: true,
}),
},
}),
},
async run(context) {
const {
title,
price,
description,
compare_at_price,
sku,
options,
variants,
} = context.propsValue;
const graphqlQuery = `
mutation mutationName($node: InputProduct!) {
createProduct(node: $node) {
id
_id
title
description
price
compare_at_price
sku
options {
id
label
type
}
variants {
id
sku
price
compare_at_price
}
created_at
updated_at
}
}
`;
const inputOptions = (options || []).map((opt: any) => {
const optionValues = (opt.options || []).map((o: any) => o.value);
return {
id: opt.id,
label: opt.label || undefined,
options: optionValues,
type: opt.type || 'text',
};
});
const inputVariants = (variants || []).map((variant: any) => ({
id: variant.id,
sku: variant.sku || undefined,
price: variant.price !== undefined ? variant.price : price,
compare_at_price: variant.compare_at_price || compare_at_price,
options: (variant.options || []).map((opt: any) => ({
id: opt.id,
value: opt.value,
})),
}));
const variables = {
node: {
title,
price,
description: description || undefined,
compare_at_price: compare_at_price || undefined,
sku: sku || undefined,
options: inputOptions,
variants: inputVariants,
},
};
const response = await lightfunnelsCommon.makeGraphQLRequest(
context.auth,
graphqlQuery,
variables
);
return response.data.createProduct;
},
});

View File

@@ -0,0 +1,50 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { lightfunnelsAuth } from '../../index';
import { lightfunnelsCommon } from '../common/index';
export const getCustomer = createAction({
auth: lightfunnelsAuth,
name: 'get_customer',
displayName: 'Get Customer',
description: 'Retrieve a specific customer by ID',
props: {
customerId: Property.ShortText({
displayName: 'Customer ID',
description: 'The ID of the customer to retrieve',
required: true,
}),
},
async run(context) {
const { customerId } = context.propsValue;
const graphqlQuery = `
query CustomerQuery($id: ID!) {
node(id: $id) {
... on Customer {
id
_id
first_name
last_name
full_name
email
phone
avatar
location
expenses
orders_count
created_at
updated_at
}
}
}
`;
const response = await lightfunnelsCommon.makeGraphQLRequest(
context.auth,
graphqlQuery,
{ id: customerId }
);
return response.data.node;
},
});

View File

@@ -0,0 +1,44 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { lightfunnelsAuth } from '../../index';
import { lightfunnelsCommon } from '../common/index';
export const getFunnel = createAction({
auth: lightfunnelsAuth,
name: 'get_funnel',
displayName: 'Get Funnel',
description: 'Retrieve a specific funnel by ID',
props: {
funnelId: Property.ShortText({
displayName: 'Funnel ID',
description: 'The ID of the funnel to retrieve',
required: true,
}),
},
async run(context) {
const { funnelId } = context.propsValue;
const graphqlQuery = `
query FunnelQuery($id: ID!) {
node(id: $id) {
... on Funnel {
id
_id
name
slug
published
created_at
updated_at
}
}
}
`;
const response = await lightfunnelsCommon.makeGraphQLRequest(
context.auth,
graphqlQuery,
{ id: funnelId }
);
return response.data.node;
},
});

View File

@@ -0,0 +1,163 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { lightfunnelsAuth } from '../../index';
import { lightfunnelsCommon } from '../common/index';
export const getOrder = createAction({
auth: lightfunnelsAuth,
name: 'get_order',
displayName: 'Get Order',
description: 'Retrieve a specific order by ID',
props: {
orderId: Property.ShortText({
displayName: 'Order ID',
description: 'The ID of the order to retrieve',
required: true,
}),
},
async run(context) {
const { orderId } = context.propsValue;
const graphqlQuery = `
query viewOrderQuery($id: ID!) {
order: node(id: $id) {
__typename
... on Order {
id
_id
total
subtotal
discount_value
normal_discount_value
bundle_discount_value
pm_discount_value
pm_extra_fees
name
notes
email
phone
archived_at
refunded_amount
paid_by_customer
net_payment
original_total
refundable
created_at
cancelled_at
test
tags
shipping
shipping_discount
customer {
id
full_name
avatar
location
}
custom
items {
__typename
... on VariantSnapshot {
product_id
id
_id
image {
path
id
}
title
price
variant_id
fulfillment_status
carrier
tracking_number
tracking_link
refund_id
payment_id
removed_at
sku
custom_options {
name
key
value
type
}
options {
id
label
value
}
}
}
payments {
id
_id
total
sub_total
created_at
refunded
refundable
price_bundle_snapshot {
id
value
discount_result
label
}
discount_snapshot {
_id
id
type
code
value
discount_result
}
refunds {
id
_id
amount
reason
}
}
shipping_address {
first_name
last_name
email
phone
line1
line2
country
city
area
zip
state
}
billing_address {
first_name
last_name
email
phone
line1
line2
country
city
area
zip
state
}
client_details {
ip
}
currency
}
}
}
`;
const response = await lightfunnelsCommon.makeGraphQLRequest(
context.auth,
graphqlQuery,
{ id: orderId }
);
return response.data.order;
},
});

View File

@@ -0,0 +1,183 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { lightfunnelsAuth } from '../../index';
import { lightfunnelsCommon } from '../common/index';
export const getProduct = createAction({
auth: lightfunnelsAuth,
name: 'get_product',
displayName: 'Get Product',
description: 'Retrieve a specific product by ID',
props: {
productId: Property.ShortText({
displayName: 'Product ID',
description: 'The ID of the product to retrieve',
required: true,
}),
},
async run(context) {
const { productId } = context.propsValue;
const graphqlQuery = `
query productQuery($id: ID!) {
product: node(id: $id) {
__typename
... on Product {
__typename
id
_id
uid
slug
title
description
notice_text
updated_at
price
sku
file_id
compare_at_price
product_type
shipping_group_id
price_bundle_id
thumbnail {
id
path
}
file {
id
title
size
path
}
options {
id
label
options
type
options_types {
option_id
value
}
}
tags {
id
title
}
images_ids
variants {
id
price
file_id
image_id
compare_at_price
sku
options {
option_id: id
value
}
enable_inventory_limit
inventory_quantity
}
order_bump {
id
title
price
sku
enabled
compare_at_price
description
image_id
file_id
}
order_bumps {
id
order_bump_product_id
variant_id
is_checked
product {
id
uid
title
description
price
compare_at_price
sku
file_id
}
}
features {
id
title
description
image_id
}
testimonials {
id
name
position
comment
image_id
}
faq {
id
question
answer
}
enable_custom_options
custom_options {
id
type
name
placeholder
}
price_bundle {
id
items {
id
label
quantity
discount_value
discount_type
}
}
default_variant {
id
uid: id
file_uid: file_id
_id
price
image {
id
path
}
compare_at_price
sku
}
inventory_quantity
enable_inventory_limit
funnels {
id
uid: id
name
slug
}
stores {
id
uid: id
name
slug
}
created_at
}
}
}
`;
const response = await lightfunnelsCommon.makeGraphQLRequest(
context.auth,
graphqlQuery,
{ id: productId },
);
return response.data.product;
},
})

View File

@@ -0,0 +1,71 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { lightfunnelsAuth } from '../../index';
import { lightfunnelsCommon } from '../common/index';
export const listCustomers = createAction({
auth: lightfunnelsAuth,
name: 'list_customers',
displayName: 'List Customers',
description: 'Retrieve a list of customers',
props: {
first: Property.Number({
displayName: 'Limit',
description: 'Number of customers to retrieve (default: 10)',
required: false,
defaultValue: 10,
}),
after: Property.ShortText({
displayName: 'After Cursor',
description: 'Cursor for pagination (optional)',
required: false,
}),
query: Property.ShortText({
displayName: 'Query',
description: 'Filter query (e.g., "order_by:created_at order_dir:desc")',
required: false,
defaultValue: 'order_by:created_at order_dir:desc',
}),
},
async run(context) {
const { first, after, query } = context.propsValue;
const graphqlQuery = `
query customersQuery($first: Int, $after: String, $query: String!) {
customers(first: $first, after: $after, query: $query) {
edges {
node {
id
_id
avatar
email
full_name
phone
updated_at
created_at
location
}
expenses
orders_count
cursor
}
pageInfo {
endCursor
hasNextPage
}
}
}
`;
const response = await lightfunnelsCommon.makeGraphQLRequest(
context.auth,
graphqlQuery,
{
first: first || 10,
after: after || null,
query: query || 'order_by:created_at order_dir:desc',
},
);
return response.data.customers;
},
});

View File

@@ -0,0 +1,89 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { lightfunnelsAuth } from '../../index';
import { lightfunnelsCommon } from '../common/index';
export const listOrders = createAction({
auth: lightfunnelsAuth,
name: 'list_orders',
displayName: 'List Orders',
description: 'Retrieve a list of orders',
props: {
first: Property.Number({
displayName: 'Limit',
description: 'Number of orders to retrieve (default: 10)',
required: false,
defaultValue: 10,
}),
after: Property.ShortText({
displayName: 'After Cursor',
description: 'Cursor for pagination (optional)',
required: false,
}),
query: Property.ShortText({
displayName: 'Query',
description: 'Filter query (e.g., "order_by:created_at order_dir:desc")',
required: false,
defaultValue: 'order_by:created_at order_dir:desc',
}),
},
async run(context) {
const { first, after, query } = context.propsValue;
const graphqlQuery = `
query ordersQuery($first: Int, $after: String, $query: String!) {
orders(first: $first, after: $after, query: $query) {
edges {
node {
id
_id
name
total
fulfillment_status
financial_status
customer {
full_name
id
}
cancelled_at
date: created_at
formattedDate: created_at(format: "LLL")
test
currency
checkout {
id
store {
id
name
}
funnel {
id
name
}
}
tags
}
cursor
}
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
}
}
`;
const response = await lightfunnelsCommon.makeGraphQLRequest(
context.auth,
graphqlQuery,
{
first: first || 10,
after: after || null,
query: query || 'order_by:created_at order_dir:desc',
},
);
return response.data.orders;
},
});

View File

@@ -0,0 +1,98 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { lightfunnelsAuth } from '../../index';
import { lightfunnelsCommon } from '../common/index';
export const listProducts = createAction({
auth: lightfunnelsAuth,
name: 'list_products',
displayName: 'List Products',
description: 'Retrieve a list of all products',
props: {
first: Property.Number({
displayName: 'Limit',
description: 'Number of products to retrieve (default: 10)',
required: false,
defaultValue: 10,
}),
after: Property.ShortText({
displayName: 'After Cursor',
description: 'Cursor for pagination (optional)',
required: false,
}),
query: Property.ShortText({
displayName: 'Query',
description: 'Filter query (e.g., "order_by:id order_dir:desc last_month")',
required: false,
defaultValue: 'order_by:id order_dir:desc',
}),
},
async run(context) {
const { first, after, query } = context.propsValue;
const graphqlQuery = `
query productsQuery($first: Int, $after: String, $query: String!) {
products(query: $query, after: $after, first: $first) {
edges {
node {
id
_id
uid
slug
title
description
notice_text
price
sku
file_id
compare_at_price
product_type
thumbnail {
id
path
}
file {
id
title
size
path
}
tags {
id
title
}
variants {
id
price
compare_at_price
sku
enable_inventory_limit
inventory_quantity
}
inventory_quantity
enable_inventory_limit
created_at
updated_at
}
cursor
}
pageInfo {
endCursor
hasNextPage
}
}
}
`;
const response = await lightfunnelsCommon.makeGraphQLRequest(
context.auth,
graphqlQuery,
{
first: first || 10,
after: after || null,
query: query || 'order_by:id order_dir:desc',
},
);
return response.data.products;
},
})