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

@@ -4,9 +4,40 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import apiClient from '../api/client';
import { Appointment, AppointmentStatus } from '../types';
import { Appointment, AppointmentStatus, Participant, ParticipantInput } from '../types';
import { format } from 'date-fns';
/**
* Transform backend participant data to frontend format
*/
function transformParticipant(p: any): Participant {
return {
id: String(p.id),
role: p.role,
userId: p.object_id && p.content_type_str?.includes('user') ? String(p.object_id) : undefined,
resourceId: p.object_id && p.content_type_str?.includes('resource') ? String(p.object_id) : undefined,
displayName: p.display_name || p.participant_display || '',
externalEmail: p.external_email || '',
externalName: p.external_name || '',
isExternal: p.is_external || false,
calendarInviteSent: p.calendar_invite_sent || false,
calendarInviteSentAt: p.calendar_invite_sent_at || undefined,
};
}
/**
* Transform frontend participant input to backend format
*/
function transformParticipantInput(p: ParticipantInput): Record<string, unknown> {
return {
role: p.role,
user_id: p.userId || null,
resource_id: p.resourceId || null,
external_email: p.externalEmail || '',
external_name: p.externalName || '',
};
}
interface AppointmentFilters {
resource?: string;
customer?: string;
@@ -62,6 +93,8 @@ export const useAppointments = (filters?: AppointmentFilters) => {
isVariablePricing: a.is_variable_pricing || false,
remainingBalance: a.remaining_balance ? parseFloat(a.remaining_balance) / 100 : null,
overpaidAmount: a.overpaid_amount ? parseFloat(a.overpaid_amount) / 100 : null,
// Participants
participants: a.participants?.map(transformParticipant) || [],
}));
},
});
@@ -103,12 +136,19 @@ export const useAppointment = (id: string) => {
isVariablePricing: data.is_variable_pricing || false,
remainingBalance: data.remaining_balance ? parseFloat(data.remaining_balance) / 100 : null,
overpaidAmount: data.overpaid_amount ? parseFloat(data.overpaid_amount) / 100 : null,
// Participants
participants: data.participants?.map(transformParticipant) || [],
};
},
enabled: !!id,
});
};
// Extended create data that includes participants
interface AppointmentCreateData extends Omit<Appointment, 'id'> {
participantsInput?: ParticipantInput[];
}
/**
* Hook to create an appointment
*/
@@ -116,7 +156,7 @@ export const useCreateAppointment = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (appointmentData: Omit<Appointment, 'id'>) => {
mutationFn: async (appointmentData: AppointmentCreateData) => {
const startTime = appointmentData.startTime;
const endTime = new Date(startTime.getTime() + appointmentData.durationMinutes * 60000);
@@ -133,6 +173,11 @@ export const useCreateAppointment = () => {
backendData.customer = parseInt(appointmentData.customerId);
}
// Include participants if provided
if (appointmentData.participantsInput && appointmentData.participantsInput.length > 0) {
backendData.participants_input = appointmentData.participantsInput.map(transformParticipantInput);
}
const { data } = await apiClient.post('/appointments/', backendData);
return data;
},
@@ -142,6 +187,11 @@ export const useCreateAppointment = () => {
});
};
// Extended update data that includes participants
interface AppointmentUpdateData extends Partial<Appointment> {
participantsInput?: ParticipantInput[];
}
/**
* Hook to update an appointment with optimistic updates for instant UI feedback
*/
@@ -149,7 +199,7 @@ export const useUpdateAppointment = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ id, updates }: { id: string; updates: Partial<Appointment> }) => {
mutationFn: async ({ id, updates }: { id: string; updates: AppointmentUpdateData }) => {
const backendData: any = {};
if (updates.serviceId) backendData.service = parseInt(updates.serviceId);
@@ -172,6 +222,11 @@ export const useUpdateAppointment = () => {
if (updates.status) backendData.status = updates.status;
if (updates.notes !== undefined) backendData.notes = updates.notes;
// Handle participants update
if (updates.participantsInput !== undefined) {
backendData.participants_input = updates.participantsInput.map(transformParticipantInput);
}
const { data } = await apiClient.patch(`/appointments/${id}/`, backendData);
return data;
},