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,302 @@
import { propsValidation } from '@activepieces/pieces-common';
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { formatDateTime, zohoBookingsAuth, zohoBookingsCommon } from '../common';
export const bookAppointment = createAction({
auth: zohoBookingsAuth,
name: 'bookAppointment',
displayName: 'Book Appointment',
description: 'Book an appointment for a customer for a desired service',
props: {
workspace_id: Property.Dropdown({
auth: zohoBookingsAuth,
displayName: 'Workspace',
description: 'Select the workspace for the appointment',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Authentication required',
options: [],
};
}
try {
const location = (auth as any).props?.['location'] || 'zoho.com';
const workspaces = await zohoBookingsCommon.fetchWorkspaces(
(auth as any).access_token,
location
);
return {
options: workspaces.map((workspace: any) => ({
label: workspace.name,
value: workspace.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load workspaces',
options: [],
};
}
},
}),
service_id: Property.Dropdown({
auth: zohoBookingsAuth,
displayName: 'Service',
description: 'Select the service for which the appointment is booked',
required: true,
refreshers: ['workspace_id'],
options: async ({ auth, workspace_id }) => {
if (!workspace_id || !auth) {
return {
disabled: true,
placeholder: 'Please enter workspace ID first',
options: [],
};
}
try {
const location = (auth as any).props?.['location'] || 'zoho.com';
const services = await zohoBookingsCommon.fetchServices(
(auth as any).access_token,
location,
workspace_id as string
);
return {
options: services.map((service: any) => ({
label: `${service.name} (${service.duration})`,
value: service.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load services',
options: [],
};
}
},
}),
staff_id: Property.Dropdown({
auth: zohoBookingsAuth,
displayName: 'Staff',
description:
'Select the staff member (use this OR resource_id OR group_id)',
required: false,
refreshers: ['service_id'],
options: async ({ auth, service_id }) => {
if (!service_id || !auth) {
return {
disabled: true,
placeholder: 'Please select service first',
options: [],
};
}
try {
const location = (auth as any).props?.['location'] || 'zoho.com';
const staff = await zohoBookingsCommon.fetchStaff(
(auth as any).access_token,
location,
service_id as string
);
return {
options: staff.map((member: any) => ({
label: `${member.name} - ${member.designation || 'Staff'}`,
value: member.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load staff',
options: [],
};
}
},
}),
resource_id: Property.Dropdown({
auth: zohoBookingsAuth,
displayName: 'Resource',
description: 'Select the resource (use this OR staff_id OR group_id)',
required: false,
refreshers: ['service_id'],
options: async ({ auth, service_id }) => {
if (!service_id || !auth) {
return {
disabled: true,
placeholder: 'Please select service first',
options: [],
};
}
try {
const location = auth.props?.['location'] as string || 'zoho.com';
const resources = await zohoBookingsCommon.fetchResources(
auth.access_token,
location,
service_id as string
);
return {
options: resources.map((resource: any) => ({
label: resource.name,
value: resource.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load resources',
options: [],
};
}
},
}),
group_id: Property.ShortText({
displayName: 'Group ID',
description:
'The unique ID of the staff group for collective booking (use this OR staff_id OR resource_id)',
required: false,
}),
from_time: Property.DateTime({
displayName: 'From Time',
description:
'The starting time for the appointment (format: mm-dd-yyyy HH:mm:ss)',
required: true,
}),
to_time: Property.DateTime({
displayName: 'To Time',
description:
'End time for resource booking (optional, format: dd-MMM-yyyy HH:mm:ss)',
required: false,
}),
timezone: Property.ShortText({
displayName: 'Timezone',
description: 'The timezone for the appointment (optional)',
required: false,
}),
customer_name: Property.ShortText({
displayName: 'Customer Name',
description: 'Name of the customer',
required: true,
}),
customer_email: Property.ShortText({
displayName: 'Customer Email',
description: 'Email address of the customer',
required: true,
}),
customer_phone: Property.ShortText({
displayName: 'Customer Phone',
description: 'Phone number of the customer',
required: true,
}),
notes: Property.LongText({
displayName: 'Notes',
description: 'Additional information about the appointment (optional)',
required: false,
}),
additional_fields: Property.Json({
displayName: 'Additional Fields',
description: 'Additional customer details as JSON object (optional)',
required: false,
}),
cost_paid: Property.Number({
displayName: 'Cost Paid',
description: 'Amount paid for the booking (optional)',
required: false,
}),
},
async run(context) {
const { auth, propsValue } = context;
const location = auth.props?.['location'] as string || 'zoho.com';
// Validate props using Zod schema
await propsValidation.validateZod(
propsValue,
zohoBookingsCommon.bookAppointmentSchema
);
// Validate that at least one of staff_id, resource_id, or group_id is provided
if (
!propsValue.staff_id &&
!propsValue.resource_id &&
!propsValue.group_id
) {
throw new Error(
'Either staff_id, resource_id, or group_id must be provided'
);
}
// Prepare customer details
const customer_details = {
name: String(propsValue.customer_name),
email: String(propsValue.customer_email),
phone_number: String(propsValue.customer_phone),
};
const customer_details_json = JSON.stringify(customer_details);
// Prepare form data
const formData = new FormData();
formData.append('service_id', propsValue.service_id as string);
formData.append('from_time', formatDateTime(propsValue.from_time));
formData.append('customer_details', customer_details_json);
// Add optional staff/resource/group ID
if (propsValue.staff_id != null && propsValue.staff_id) {
formData.append('staff_id', propsValue.staff_id as string);
}
if (propsValue.resource_id != null && propsValue.resource_id) {
formData.append('resource_id', propsValue.resource_id as string);
}
if (propsValue.group_id != null && propsValue.group_id) {
formData.append('group_id', propsValue.group_id as string);
}
// Add optional fields
if (propsValue.to_time) {
formData.append('to_time', formatDateTime(propsValue.to_time));
}
if (propsValue.timezone) {
formData.append('timezone', propsValue.timezone);
}
if (propsValue.notes) {
formData.append('notes', propsValue.notes);
}
if (propsValue.additional_fields) {
formData.append(
'additional_fields',
JSON.stringify(propsValue.additional_fields)
);
}
if (propsValue.cost_paid) {
formData.append(
'payment_info',
JSON.stringify({ cost_paid: propsValue.cost_paid.toString() })
);
}
const response = await httpClient.sendRequest({
method: HttpMethod.POST,
url: `${zohoBookingsCommon.baseUrl(location)}/appointment`,
headers: {
Authorization: `Zoho-oauthtoken ${auth.access_token}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
body: formData,
});
if (response.body.response.status === 'failure') {
throw new Error(`${response.body.response.errormessage}`);
}
return response.body.response;
},
});

View File

@@ -0,0 +1,54 @@
import { propsValidation } from '@activepieces/pieces-common';
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { bookingIdDropdown, zohoBookingsAuth, zohoBookingsCommon } from '../common';
export const cancelAppointment = createAction({
auth: zohoBookingsAuth,
name: 'cancelAppointment',
displayName: 'Cancel Appointment',
description:
'Update the status of a booking (cancel, complete, or mark as no-show)',
props: {
from_time: Property.DateTime({
displayName: 'From Time',
description:
'The starting time for the appointment (format: mm-dd-yyyy HH:mm:ss)',
required: true,
}),
to_time: Property.DateTime({
displayName: 'To Time',
description:
'The ending time for the appointment (format: mm-dd-yyyy HH:mm:ss)',
required: false,
}),
booking_id: bookingIdDropdown,
},
async run(context) {
const { auth, propsValue } = context;
const location = auth.props?.['location'] as string || 'zoho.com';
// Validate props using Zod schema
await propsValidation.validateZod(
propsValue,
zohoBookingsCommon.cancelAppointmentSchema
);
// Prepare form data
const formData = new FormData();
formData.append('booking_id', propsValue.booking_id as string);
formData.append('action', 'cancel');
const response = await httpClient.sendRequest({
method: HttpMethod.POST,
url: `${zohoBookingsCommon.baseUrl(location)}/updateappointment`,
headers: {
Authorization: `Zoho-oauthtoken ${auth.access_token}`,
contentType: 'application/x-www-form-urlencoded',
},
body: formData,
});
return response.body;
},
});

View File

@@ -0,0 +1,223 @@
import { propsValidation } from '@activepieces/pieces-common';
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { zohoBookingsAuth, zohoBookingsCommon } from '../common';
export const fetchAvailability = createAction({
auth: zohoBookingsAuth,
name: 'fetchAvailability',
displayName: 'Fetch Availability',
description: 'Fetch availability of appointments across services',
props: {
workspace_id: Property.Dropdown({
auth: zohoBookingsAuth,
displayName: 'Workspace',
description: 'Select the workspace to fetch availability for',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Authentication required',
options: [],
};
}
try {
const location = (auth as any).props?.['location'] || 'zoho.com';
const workspaces = await zohoBookingsCommon.fetchWorkspaces(
(auth as any).access_token,
location
);
return {
options: workspaces.map((workspace: any) => ({
label: workspace.name,
value: workspace.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load workspaces',
options: [],
};
}
},
}),
service_id: Property.Dropdown({
auth: zohoBookingsAuth,
displayName: 'Service',
description: 'Select the service for which availability is to be fetched',
required: true,
refreshers: ['workspace_id'],
options: async ({ auth, workspace_id }) => {
if (!workspace_id || !auth) {
return {
disabled: true,
placeholder: 'Please enter workspace ID first',
options: [],
};
}
try {
const location = (auth as any).props?.['location'] || 'zoho.com';
const services = await zohoBookingsCommon.fetchServices(
(auth as any).access_token,
location,
workspace_id as string
);
return {
options: services.map((service: any) => ({
label: `${service.name} (${service.duration})`,
value: service.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load services',
options: [],
};
}
},
}),
staff_id: Property.Dropdown({
auth: zohoBookingsAuth,
displayName: 'Staff',
description: 'Select the staff member (use this OR group_id OR resource_id)',
required: false,
refreshers: ['service_id'],
options: async ({ auth, service_id }) => {
if (!service_id || !auth) {
return {
disabled: true,
placeholder: 'Please select service first',
options: [],
};
}
try {
const location = (auth as any).props?.['location'] || 'zoho.com';
const staff = await zohoBookingsCommon.fetchStaff(
(auth as any).access_token,
location,
service_id as string
);
return {
options: staff.map((member: any) => ({
label: `${member.name} - ${member.designation || 'Staff'}`,
value: member.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load staff',
options: [],
};
}
},
}),
group_id: Property.ShortText({
displayName: 'Group ID',
description: 'The unique ID of the staff group associated with the service (use this OR staff_id OR resource_id)',
required: false,
}),
resource_id: Property.Dropdown({
auth: zohoBookingsAuth,
displayName: 'Resource',
description: 'Select the resource (use this OR staff_id OR group_id)',
required: false,
refreshers: ['service_id'],
options: async ({ auth, service_id }) => {
if (!service_id || !auth) {
return {
disabled: true,
placeholder: 'Please select service first',
options: [],
};
}
try {
const location = (auth as any).props?.['location'] || 'zoho.com';
const resources = await zohoBookingsCommon.fetchResources(
(auth as any).access_token,
location,
service_id as string
);
return {
options: resources.map((resource: any) => ({
label: resource.name,
value: resource.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load resources',
options: [],
};
}
},
}),
selected_date: Property.DateTime({
displayName: 'Selected Date',
description: 'The date on which services are checked for availability (format: mm-dd-yyyy)',
required: true,
}),
},
async run(context) {
const { auth, propsValue } = context;
const location = auth.props?.['location'] as string || 'zoho.com';
// Validate props using Zod schema
await propsValidation.validateZod(propsValue, zohoBookingsCommon.fetchAvailabilitySchema);
// Validate that at least one of staff_id, group_id, or resource_id is provided
if (!propsValue.staff_id && !propsValue.group_id && !propsValue.resource_id) {
throw new Error('Either staff_id, group_id, or resource_id must be provided');
}
// Format date to YYYY-MM-DD format
const formatDate = (date: string) => {
const d = new Date(date);
const year = d.getFullYear();
const month = (d.getMonth() + 1).toString().padStart(2, '0');
const day = d.getDate().toString().padStart(2, '0');
return `${year}-${month}-${day}`;
};
// Prepare query parameters
const queryParams: Record<string, string> = {
service_id: propsValue.service_id,
selected_date: formatDate(propsValue.selected_date),
};
// Add the staff/group/resource ID (only one should be provided)
if (propsValue.staff_id) {
queryParams['staff_id'] = propsValue.staff_id;
}
if (propsValue.group_id) {
queryParams['group_id'] = propsValue.group_id;
}
if (propsValue.resource_id) {
queryParams['resource_id'] = propsValue.resource_id;
}
const response = await httpClient.sendRequest({
method: HttpMethod.GET,
url: `${zohoBookingsCommon.baseUrl(location)}/availableslots`,
headers: {
Authorization: `Zoho-oauthtoken ${auth.access_token}`,
},
queryParams,
});
return response.body;
},
});

View File

@@ -0,0 +1,45 @@
import { propsValidation } from '@activepieces/pieces-common';
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { bookingIdDropdown, zohoBookingsAuth, zohoBookingsCommon } from '../common';
export const getAppointmentDetails = createAction({
auth: zohoBookingsAuth,
name: 'getAppointmentDetails',
displayName: 'Get Appointment Details',
description: 'Get details of an appointment using its booking ID',
props: {
from_time: Property.DateTime({
displayName: 'From Time',
description: 'The start time of the appointment (in ISO 8601 format)',
required: true,
}),
to_time: Property.DateTime({
displayName: 'To Time',
description: 'The end time of the appointment (in ISO 8601 format)',
required: false,
}),
booking_id: bookingIdDropdown
},
async run(context) {
const { auth, propsValue } = context;
const location = auth.props?.['location'] as string || 'zoho.com';
// Validate props using Zod schema
await propsValidation.validateZod(propsValue, zohoBookingsCommon.getAppointmentDetailsSchema);
const response = await httpClient.sendRequest({
method: HttpMethod.GET,
url: `${zohoBookingsCommon.baseUrl(location)}/getappointment`,
headers: {
Authorization: `Zoho-oauthtoken ${auth.access_token}`,
},
queryParams: {
booking_id: propsValue.booking_id as string,
},
});
return response.body;
},
});

View File

@@ -0,0 +1,178 @@
import { propsValidation } from '@activepieces/pieces-common';
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import {
bookingIdDropdown,
formatDateTime,
zohoBookingsAuth,
zohoBookingsCommon,
} from '../common';
import { access } from 'fs';
export const rescheduleAppointment = createAction({
auth: zohoBookingsAuth,
name: 'rescheduleAppointment',
displayName: 'Reschedule Appointment',
description:
'Reschedule an appointment to a different time or to a different staff',
props: {
from_time: Property.DateTime({
displayName: 'From Time',
description:
'Start of the date range used to fetch existing bookings (to help you select the Booking ID to reschedule). Not sent to the reschedule API. Format: dd-MMM-yyyy HH:mm:ss',
required: true,
}),
to_time: Property.DateTime({
displayName: 'To Time',
description:
'End of the date range used to fetch existing bookings (optional). Not sent to the reschedule API. Format: dd-MMM-yyyy HH:mm:ss',
required: false,
}),
booking_id: bookingIdDropdown,
service_id: Property.Dropdown({
auth: zohoBookingsAuth,
displayName: 'Service (Optional)',
description: 'Select service to filter staff options',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Authentication required',
options: [],
};
}
try {
const location = (auth as any).props?.['location'] || 'zoho.com';
const services = await zohoBookingsCommon.fetchServices(
(auth as any).access_token,
location
);
return {
options: services.map((service: any) => ({
label: `${service.name} (${service.duration})`,
value: service.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load services',
options: [],
};
}
},
}),
staff_id: Property.Dropdown({
auth: zohoBookingsAuth,
displayName: 'Staff',
description:
'Select the staff to reschedule to (use this OR group_id OR start_time)',
required: false,
refreshers: ['service_id'],
options: async ({ auth, service_id }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Authentication required',
options: [],
};
}
try {
const location = (auth as any).props?.['location'] || 'zoho.com';
const staff = await zohoBookingsCommon.fetchStaff(
(auth as any).access_token,
location,
service_id as string
);
return {
options: staff.map((member: any) => ({
label: `${member.name} - ${member.designation || 'Staff'}`,
value: member.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load staff',
options: [],
};
}
},
}),
group_id: Property.ShortText({
displayName: 'Group ID',
description:
'The unique ID of the staff group to reschedule to (use this OR staff_id OR start_time)',
required: false,
}),
start_time: Property.DateTime({
displayName: 'New Start Time',
description:
'The new time to reschedule the appointment to (format: dd-MMM-yyyy HH:mm:ss, use this OR staff_id OR group_id)',
required: true,
}),
},
async run(context) {
const { auth, propsValue } = context;
const location = auth.props?.['location'] as string || 'zoho.com';
// Validate props using Zod schema
await propsValidation.validateZod(
propsValue,
zohoBookingsCommon.rescheduleAppointmentSchema
);
// Validate that at least one of staff_id, group_id, or start_time is provided
if (
!propsValue.staff_id &&
!propsValue.group_id &&
!propsValue.start_time
) {
throw new Error(
'Either staff_id, group_id, or start_time must be provided'
);
}
// Prepare form data
const formData = new FormData();
formData.append('booking_id', propsValue.booking_id as string);
// Add the reschedule parameter (only one should be provided)
if (propsValue.staff_id) {
formData.append('staff_id', propsValue.staff_id);
}
if (propsValue.group_id) {
formData.append('group_id', propsValue.group_id);
}
if (propsValue.start_time) {
formData.append('start_time', formatDateTime(propsValue.start_time));
}
const response = await httpClient.sendRequest({
method: HttpMethod.POST,
url: `${zohoBookingsCommon.baseUrl(location)}/rescheduleappointment`,
headers: {
Authorization: `Zoho-oauthtoken ${auth.access_token}`,
contentType: 'application/x-www-form-urlencoded',
},
body: formData,
});
console.log(auth.access_token)
const responseBody = response.body.response;
if (responseBody.status !== 'success') {
throw new Error(
`Failed to reschedule appointment: ${responseBody.message || 'Unknown error'}`
);
}
if (responseBody.returnvalue.Status == "failure") {
throw new Error(responseBody.returnvalue.message || 'Failed to reschedule appointment');
}
return responseBody;
},
});

View File

@@ -0,0 +1,257 @@
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { PieceAuth, Property } from '@activepieces/pieces-framework';
import { OAuth2GrantType } from '@activepieces/shared';
import * as schemas from './schemas';
export const zohoBookingsAuth = PieceAuth.OAuth2({
props: {
location: Property.StaticDropdown({
displayName: 'Data Center',
description: 'The data center location of your Zoho Bookings account',
required: true,
options: {
options: [
{
label: 'zoho.com (United States)',
value: 'zoho.com',
},
{
label: 'zoho.eu (Europe)',
value: 'zoho.eu',
},
{
label: 'zoho.in (India)',
value: 'zoho.in',
},
{
label: 'zoho.com.au (Australia)',
value: 'zoho.com.au',
},
{
label: 'zoho.jp (Japan)',
value: 'zoho.jp',
},
{
label: 'zoho.com.cn (China)',
value: 'zoho.com.cn',
},
],
},
}),
},
description: 'Connect your Zoho Bookings account using OAuth2',
grantType: OAuth2GrantType.AUTHORIZATION_CODE,
required: true,
authUrl: 'https://accounts.{location}/oauth/v2/auth',
tokenUrl: 'https://accounts.{location}/oauth/v2/token',
scope: ['zohobookings.data.CREATE', 'zohobookings.data.READ'],
});
export const zohoBookingsCommon = {
baseUrl: (location = 'zoho.com') => {
return `https://www.zohoapis.${location
.substring(5)
.trim()}/bookings/v1/json`;
},
baseHeaders: (accessToken: string) => {
return {
Authorization: `Zoho-oauthtoken ${accessToken}`,
'Content-Type': 'application/json',
};
},
// API Methods
async fetchWorkspaces(accessToken: string, location: string) {
const response = await httpClient.sendRequest({
method: HttpMethod.GET,
url: `${this.baseUrl(location)}/workspaces`,
headers: this.baseHeaders(accessToken),
});
return response.body?.response?.returnvalue?.data || [];
},
async fetchServices(
accessToken: string,
location: string,
workspaceId?: string
) {
const queryParams: Record<string, string> = {};
if (workspaceId) {
queryParams['workspace_id'] = workspaceId;
}
const response = await httpClient.sendRequest({
method: HttpMethod.GET,
url: `${this.baseUrl(location)}/services`,
headers: this.baseHeaders(accessToken),
queryParams,
});
return response.body?.response?.returnvalue?.data || [];
},
async fetchResources(
accessToken: string,
location: string,
serviceId?: string
) {
const queryParams: Record<string, string> = {};
if (serviceId) {
queryParams['service_id'] = serviceId;
}
const response = await httpClient.sendRequest({
method: HttpMethod.GET,
url: `${this.baseUrl(location)}/resources`,
headers: this.baseHeaders(accessToken),
queryParams,
});
return response.body?.response?.returnvalue?.data || [];
},
async fetchStaff(accessToken: string, location: string, serviceId?: string) {
const queryParams: Record<string, string> = {};
if (serviceId) {
queryParams['service_id'] = serviceId;
}
const response = await httpClient.sendRequest({
method: HttpMethod.GET,
url: `${this.baseUrl(location)}/staffs`,
headers: this.baseHeaders(accessToken),
queryParams,
});
return response.body?.response?.returnvalue?.data || [];
},
async fetchAppointments(
accessToken: string,
location: string,
options?: {
serviceId?: string;
staffId?: string;
status?: string;
from_time?: string;
to_time?: string;
page?: number;
perPage?: number;
}
) {
const payload: Record<string, string | number> = {};
// if (options?.staffId !== undefined) payload['staff_id'] = Number(options.staffId);
// if (options?.serviceId) payload['service_id'] = options.serviceId;
if (options?.from_time) payload['from_time'] = options.from_time;
// if (options?.to_time) payload['to_time'] = options.to_time;
// if (options?.status) payload['status'] = options.status;
// if (options?.page != null) payload['page'] = options.page;
// if (options?.perPage != null) payload['per_page'] = options.perPage;
// if (options?.need_customer_more_info != null) {
// payload.need_customer_more_info = String(options.need_customer_more_info);
// }
// if (options?.customer_name) payload['customer_name'] = options.customer_name;
// if (options?.customer_email) payload['customer_email'] = options.customer_email;
// Send as multipart/form-data with a single `data` field
const formData = new FormData();
formData.append('data', JSON.stringify(payload));
const response = await httpClient.sendRequest({
method: HttpMethod.POST,
url: `${this.baseUrl(location)}/fetchappointment`,
headers: {
Authorization: `Zoho-oauthtoken ${accessToken}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
body: formData,
});
console.log(JSON.stringify(response.body.response));
return response.body?.response?.returnvalue?.response || [];
},
// Schemas
bookAppointmentSchema: schemas.bookAppointment,
rescheduleAppointmentSchema: schemas.rescheduleAppointment,
fetchAvailabilitySchema: schemas.fetchAvailability,
getAppointmentDetailsSchema: schemas.getAppointmentDetails,
cancelAppointmentSchema: schemas.cancelAppointment,
};
export const formatDateTime = (date: string) => {
const d = new Date(date);
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
];
const day = d.getDate().toString().padStart(2, '0');
const month = months[d.getMonth()];
const year = d.getFullYear();
const hours = d.getHours().toString().padStart(2, '0');
const minutes = d.getMinutes().toString().padStart(2, '0');
const seconds = d.getSeconds().toString().padStart(2, '0');
return `${day}-${month}-${year} ${hours}:${minutes}:${seconds}`;
};
export const bookingIdDropdown = Property.Dropdown({
auth: zohoBookingsAuth,
displayName: 'Appointment',
description: 'Select the appointment to get details for',
required: true,
refreshers: ['from_time', 'to_time'],
options: async ({ auth, from_time, to_time }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Authentication required',
options: [],
};
}
if (!from_time) {
return {
disabled: true,
placeholder: 'Please select From Time first',
options: [],
};
}
const formattedFromTime = formatDateTime(from_time as string);
try {
const location = (auth as any).props?.['location'] || 'zoho.com';
const appointments = await zohoBookingsCommon.fetchAppointments(
(auth as any).access_token,
location,
{
perPage: 50,
from_time: formattedFromTime,
to_time: to_time ? formatDateTime(to_time as string) : undefined,
}
);
return {
options: appointments.map((appointment: any) => ({
label: `${appointment.booking_id} - ${appointment.customer_name} (${appointment.service_name}) - ${appointment.start_time} [${appointment.status}]`,
value: appointment.booking_id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load appointments',
options: [],
};
}
},
});

View File

@@ -0,0 +1,44 @@
import z from 'zod';
export const bookAppointment = {
workspace_id: z.string().min(1),
service_id: z.string().min(1),
staff_id: z.string().optional(),
resource_id: z.string().optional(),
group_id: z.string().optional(),
from_time: z.string(),
to_time: z.string().optional(),
timezone: z.string().optional(),
customer_name: z.string().min(1),
customer_email: z.string().email(),
customer_phone: z.string().min(1),
notes: z.string().optional(),
additional_fields: z.record(z.string(), z.unknown()).optional(),
cost_paid: z.number().min(0).optional(),
};
export const rescheduleAppointment = {
booking_id: z.string().min(1),
service_id: z.string().optional(),
staff_id: z.string().optional(),
group_id: z.string().optional(),
start_time: z.string().optional(),
};
export const fetchAvailability = {
workspace_id: z.string().min(1),
service_id: z.string().min(1),
staff_id: z.string().optional(),
group_id: z.string().optional(),
resource_id: z.string().optional(),
selected_date: z.string(),
};
export const getAppointmentDetails = {
booking_id: z.string().min(1),
};
export const cancelAppointment = {
booking_id: z.string().min(1),
action: z.enum(['cancel', 'completed', 'noshow']),
};