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,234 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { t } from 'i18next';
|
||||
import { useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
} from '@/components/ui/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormMessage,
|
||||
FormDescription,
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { internalErrorToast } from '@/components/ui/sonner';
|
||||
import { useAuthorization } from '@/hooks/authorization-hooks';
|
||||
import { platformHooks } from '@/hooks/platform-hooks';
|
||||
import { projectHooks } from '@/hooks/project-hooks';
|
||||
import { userHooks } from '@/hooks/user-hooks';
|
||||
import { api } from '@/lib/api';
|
||||
import { projectApi } from '@/lib/project-api';
|
||||
import {
|
||||
Permission,
|
||||
PlatformRole,
|
||||
ProjectWithLimits,
|
||||
ApErrorParams,
|
||||
ErrorCode,
|
||||
TeamProjectsLimit,
|
||||
} from '@activepieces/shared';
|
||||
|
||||
interface EditProjectDialogProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
projectId: string;
|
||||
initialValues?: {
|
||||
projectName?: string;
|
||||
aiCredits?: string;
|
||||
externalId?: string;
|
||||
};
|
||||
renameOnly?: boolean;
|
||||
}
|
||||
|
||||
type FormValues = {
|
||||
projectName: string;
|
||||
aiCredits: string;
|
||||
externalId?: string;
|
||||
};
|
||||
|
||||
export function EditProjectDialog({
|
||||
open,
|
||||
onClose,
|
||||
projectId,
|
||||
initialValues,
|
||||
renameOnly = false,
|
||||
}: EditProjectDialogProps) {
|
||||
const { checkAccess } = useAuthorization();
|
||||
const { platform } = platformHooks.useCurrentPlatform();
|
||||
const platformRole = userHooks.getCurrentUserPlatformRole();
|
||||
const queryClient = useQueryClient();
|
||||
const { updateCurrentProject } = projectHooks.useCurrentProject();
|
||||
|
||||
const form = useForm<FormValues>({
|
||||
defaultValues: {
|
||||
projectName: initialValues?.projectName,
|
||||
aiCredits: initialValues?.aiCredits || '',
|
||||
externalId: initialValues?.externalId,
|
||||
},
|
||||
disabled: checkAccess(Permission.WRITE_PROJECT) === false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
form.reset(initialValues);
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
const mutation = useMutation<
|
||||
ProjectWithLimits,
|
||||
Error,
|
||||
{
|
||||
displayName: string;
|
||||
externalId?: string;
|
||||
}
|
||||
>({
|
||||
mutationFn: (request) => {
|
||||
updateCurrentProject(queryClient, request);
|
||||
return projectApi.update(projectId, {
|
||||
...request,
|
||||
externalId:
|
||||
request.externalId?.trim() !== '' ? request.externalId : undefined,
|
||||
});
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast.success(t('Your changes have been saved.'), {
|
||||
duration: 3000,
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ['current-project'],
|
||||
});
|
||||
onClose();
|
||||
},
|
||||
onError: (error) => {
|
||||
if (api.isError(error)) {
|
||||
const apError = error.response?.data as ApErrorParams;
|
||||
switch (apError.code) {
|
||||
case ErrorCode.PROJECT_EXTERNAL_ID_ALREADY_EXISTS: {
|
||||
form.setError('root.serverError', {
|
||||
message: t('The external ID is already taken.'),
|
||||
});
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
internalErrorToast();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-md w-full">
|
||||
<DialogTitle>
|
||||
{renameOnly ? t('Rename Project') : t('Edit Project')}
|
||||
</DialogTitle>
|
||||
<p className="text-sm text-muted-foreground mb-4 mt-1">
|
||||
{renameOnly
|
||||
? null
|
||||
: t('Update your project settings and configuration.')}
|
||||
</p>
|
||||
<Form {...form}>
|
||||
<form
|
||||
className="space-y-4"
|
||||
onSubmit={form.handleSubmit((values) =>
|
||||
mutation.mutate({
|
||||
displayName: values.projectName,
|
||||
externalId: values.externalId,
|
||||
}),
|
||||
)}
|
||||
>
|
||||
<FormField
|
||||
name="projectName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<Label htmlFor="projectName">{t('Project Name')}</Label>
|
||||
<Input
|
||||
{...field}
|
||||
id="projectName"
|
||||
placeholder={t('Project Name')}
|
||||
className="rounded-sm"
|
||||
/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{!renameOnly &&
|
||||
platform.plan.teamProjectsLimit !== TeamProjectsLimit.NONE && (
|
||||
<FormField
|
||||
name="aiCredits"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<Label htmlFor="aiCredits">{t('AI Credits')}</Label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
{...field}
|
||||
type="number"
|
||||
id="aiCredits"
|
||||
placeholder={t('AI Credits')}
|
||||
className="rounded-sm pr-16"
|
||||
/>
|
||||
{!field.disabled && (
|
||||
<Button
|
||||
variant="link"
|
||||
type="button"
|
||||
tabIndex={-1}
|
||||
className="absolute right-1 top-1/2 -translate-y-1/2 text-xs px-2 py-1 h-7"
|
||||
onClick={() => form.setValue('aiCredits', '')}
|
||||
>
|
||||
{t('Clear')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!renameOnly &&
|
||||
platform.plan.embeddingEnabled &&
|
||||
platformRole === PlatformRole.ADMIN && (
|
||||
<FormField
|
||||
name="externalId"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<Label htmlFor="externalId">{t('External ID')}</Label>
|
||||
<FormDescription>
|
||||
{t(
|
||||
'Used to identify the project based on your SaaS ID',
|
||||
)}
|
||||
</FormDescription>
|
||||
<Input
|
||||
{...field}
|
||||
id="externalId"
|
||||
placeholder={t('org-3412321')}
|
||||
className="rounded-sm"
|
||||
/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{checkAccess(Permission.WRITE_PROJECT) && (
|
||||
<DialogFooter className="justify-end mt-6">
|
||||
<Button type="submit">{t('Save')}</Button>
|
||||
</DialogFooter>
|
||||
)}
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { CheckIcon } from '@radix-ui/react-icons';
|
||||
import { t } from 'i18next';
|
||||
import * as React from 'react';
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { authenticationSession } from '@/lib/authentication-session';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
import { ScrollArea } from '../../../components/ui/scroll-area';
|
||||
import { platformHooks } from '../../../hooks/platform-hooks';
|
||||
import { projectHooks } from '../../../hooks/project-hooks';
|
||||
|
||||
export function PlatformSwitcher({ children }: { children: React.ReactNode }) {
|
||||
const { data: allProjects } = projectHooks.useProjectsForPlatforms();
|
||||
const { platform: currentPlatform } = platformHooks.useCurrentPlatform();
|
||||
|
||||
const platforms = React.useMemo(() => {
|
||||
if (!allProjects) return [];
|
||||
return allProjects.map((platform) => ({
|
||||
name: platform.platformName,
|
||||
id: platform.projects[0]?.platformId,
|
||||
}));
|
||||
}, [allProjects]);
|
||||
|
||||
const handlePlatformSwitch = async (platformId: string) => {
|
||||
await authenticationSession.switchToPlatform(platformId);
|
||||
};
|
||||
|
||||
const dropdownContent = (
|
||||
<DropdownMenuContent
|
||||
className="w-56 rounded-lg"
|
||||
align="start"
|
||||
side="right"
|
||||
sideOffset={4}
|
||||
>
|
||||
<div className="px-2 py-1.5">
|
||||
<p className="text-xs text-muted-foreground">{t('Accounts')}</p>
|
||||
</div>
|
||||
<ScrollArea viewPortClassName="max-h-[400px]">
|
||||
{platforms.map((platform) => (
|
||||
<DropdownMenuItem
|
||||
key={platform.id}
|
||||
onClick={() => handlePlatformSwitch(platform.id)}
|
||||
className="text-sm p-2 break-all cursor-pointer"
|
||||
>
|
||||
{platform.name}
|
||||
<CheckIcon
|
||||
className={cn(
|
||||
'ml-auto h-4 w-4 shrink-0',
|
||||
currentPlatform?.id === platform.id
|
||||
? 'opacity-100'
|
||||
: 'opacity-0',
|
||||
)}
|
||||
/>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</ScrollArea>
|
||||
</DropdownMenuContent>
|
||||
);
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger>
|
||||
{dropdownContent}
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user