Skip to content

Commit

Permalink
feat: add email receivers to workflow form
Browse files Browse the repository at this point in the history
Signed-off-by: Ryan Hopper-Lowe <[email protected]>
  • Loading branch information
ryanhopperlowe committed Jan 8, 2025
1 parent a49cac5 commit 0d3b161
Show file tree
Hide file tree
Showing 15 changed files with 300 additions and 52 deletions.
2 changes: 1 addition & 1 deletion ui/admin/app/components/agent/AgentDropdownActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export function AgentDropdownActions({ agent }: { agent: Agent }) {
return (
<>
<DropdownMenu modal>
<DropdownMenuTrigger>
<DropdownMenuTrigger asChild>
<Button size="icon" variant="ghost">
<EllipsisVerticalIcon className="w-4 h-4" />
</Button>
Expand Down
2 changes: 1 addition & 1 deletion ui/admin/app/components/agent/AgentPublishStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export function AgentPublishStatus({
className="h-8 text-muted-foreground text-sm bg-background flex-row-reverse"
holdStatusDelay={6000}
text={agentUrl}
iconOnly
hideText
/>

<Link
Expand Down
72 changes: 47 additions & 25 deletions ui/admin/app/components/composed/CopyText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,30 @@ import {
TooltipTrigger,
} from "~/components/ui/tooltip";

type CopyTextProps = {
text: string;
displayText?: string;
className?: string;
holdStatusDelay?: number;
hideText?: boolean;
hideIcon?: boolean;
classNames?: {
root?: string;
textWrapper?: string;
text?: string;
icon?: string;
};
};

export function CopyText({
text,
displayText = text,
className,
holdStatusDelay,
iconOnly,
}: {
text: string;
displayText?: string;
className?: string;
holdStatusDelay?: number;
iconOnly?: boolean;
}) {
hideText,
hideIcon,
classNames = {},
}: CopyTextProps) {
const [isCopied, setIsCopied] = useState(false);

useEffect(() => {
Expand All @@ -38,17 +49,26 @@ export function CopyText({
<div
className={cn(
"flex items-center gap-2 bg-accent rounded-md w-fit overflow-hidden",
className
className,
classNames.root
)}
>
{!iconOnly && (
{!hideText && (
<Tooltip>
<TooltipTrigger
type="button"
onClick={() => handleCopy(text)}
className="decoration-dotted underline-offset-4 underline text-ellipsis overflow-hidden text-nowrap"
className={cn(
"decoration-dotted underline-offset-4 underline text-ellipsis overflow-hidden text-nowrap",
classNames.textWrapper
)}
>
<p className="truncate break-words p-2">
<p
className={cn(
"truncate break-words p-2",
classNames.text
)}
>
{displayText}
</p>
</TooltipTrigger>
Expand All @@ -60,19 +80,21 @@ export function CopyText({
</Tooltip>
)}

<Button
size="icon"
onClick={() => handleCopy(text)}
className="aspect-square"
variant="ghost"
type="button"
>
{isCopied ? (
<ClipboardCheckIcon className="text-success" />
) : (
<ClipboardIcon />
)}
</Button>
{!hideIcon && (
<Button
size="icon"
onClick={() => handleCopy(text)}
className={cn("aspect-square", classNames.icon)}
variant="ghost"
type="button"
>
{isCopied ? (
<ClipboardCheckIcon className="text-success" />
) : (
<ClipboardIcon />
)}
</Button>
)}
</div>
);

Expand Down
10 changes: 5 additions & 5 deletions ui/admin/app/components/composed/typography.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function Truncate({
className,
asChild,
disableTooltip,
tooltipContent = <p>{children}</p>,
tooltipContent = children,
}: {
children: React.ReactNode;
className?: string;
Expand All @@ -23,18 +23,18 @@ export function Truncate({
}) {
const Comp = asChild ? Slot : "p";

const content = <Comp className="truncate">{children}</Comp>;

if (disableTooltip) {
return <Comp className="truncate">{children}</Comp>;
return content;
}

return (
<Tooltip>
<TooltipContent>{tooltipContent}</TooltipContent>

<TooltipTrigger asChild>
<div className={cn("cursor-pointer", className)}>
<Comp className="truncate">{children}</Comp>
</div>
<div className={cn("cursor-pointer", className)}>{content}</div>
</TooltipTrigger>
</Tooltip>
);
Expand Down
2 changes: 1 addition & 1 deletion ui/admin/app/components/tools/ToolIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function ToolIcon(props: ToolIconProps) {
}

return (
<Tooltip delayDuration={200}>
<Tooltip>
<TooltipTrigger>{content}</TooltipTrigger>

<TooltipContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
export function CreateWorkflowTrigger() {
return (
<Dialog>
<DialogTrigger>
<DialogTrigger asChild>
<Button>
<PlusIcon /> Create Trigger
</Button>
Expand Down
35 changes: 21 additions & 14 deletions ui/admin/app/components/workflow-triggers/EmailReceiverForm.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { useNavigate } from "react-router";
import { $path } from "safe-routes";
import { toast } from "sonner";
import useSWR, { mutate } from "swr";
import { z } from "zod";
Expand Down Expand Up @@ -32,29 +30,34 @@ const formSchema = z.object({
description: z.string(),
alias: z.string(),
workflow: z.string().min(1, "Workflow is required"),
allowedSenders: z.array(z.string()),
allowedSenders: z.array(z.string()).optional(),
});

export type EmailRecieverFormValues = z.infer<typeof formSchema>;

type EmailRecieverFormProps = {
emailReceiver?: EmailReceiver;
emailReceiver?: Partial<EmailReceiver>;
onContinue?: () => void;
hideTitle?: boolean;
};

export function EmailReceiverForm({ emailReceiver }: EmailRecieverFormProps) {
const navigate = useNavigate();
export function EmailReceiverForm({
emailReceiver,
onContinue,
hideTitle,
}: EmailRecieverFormProps) {
const getWorkflows = useSWR(WorkflowService.getWorkflows.key(), () =>
WorkflowService.getWorkflows()
);

const handleSubmitSuccess = () => {
if (emailReceiver) {
if (emailReceiver?.id) {
mutate(
EmailReceiverApiService.getEmailReceiverById(emailReceiver.id)
);
}
mutate(EmailReceiverApiService.getEmailReceivers.key());
navigate($path("/workflow-triggers"));
onContinue?.();
};

const form = useForm<EmailRecieverFormValues>({
Expand All @@ -69,6 +72,8 @@ export function EmailReceiverForm({ emailReceiver }: EmailRecieverFormProps) {
},
});

const { handleSubmit, reset } = form;

const createEmailReceiver = useAsync(
EmailReceiverApiService.createEmailReceiver,
{
Expand All @@ -91,11 +96,11 @@ export function EmailReceiverForm({ emailReceiver }: EmailRecieverFormProps) {

useEffect(() => {
if (emailReceiver) {
form.reset(emailReceiver);
reset(emailReceiver);
}
}, [emailReceiver, form]);
}, [emailReceiver, reset]);

const handleSubmit = form.handleSubmit((values: EmailRecieverFormValues) =>
const onSubmit = handleSubmit((values: EmailRecieverFormValues) =>
emailReceiver?.id
? updateEmailReceiver.execute(emailReceiver.id, values)
: createEmailReceiver.execute(values)
Expand All @@ -111,9 +116,11 @@ export function EmailReceiverForm({ emailReceiver }: EmailRecieverFormProps) {
<Form {...form}>
<form
className="space-y-8 p-8 max-w-3xl mx-auto"
onSubmit={handleSubmit}
onSubmit={onSubmit}
>
<h2>{isEdit ? "Edit" : "Create"} Email Receiver</h2>
{!hideTitle && (
<h2>{isEdit ? "Edit" : "Create"} Email Trigger</h2>
)}

<ControlledInput
control={form.control}
Expand Down Expand Up @@ -162,7 +169,7 @@ export function EmailReceiverForm({ emailReceiver }: EmailRecieverFormProps) {
disabled={loading}
loading={loading}
>
{isEdit ? "Update" : "Create"} Email Receiver
{isEdit ? "Update" : "Create"} Email Trigger
</Button>
</form>
</Form>
Expand Down
63 changes: 63 additions & 0 deletions ui/admin/app/components/workflow/DeleteWorkflowEmailReceiver.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { TrashIcon } from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
import { mutate } from "swr";

import { EmailReceiverApiService } from "~/lib/service/api/emailReceiverApiService";

import { Button } from "~/components/ui/button";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "~/components/ui/popover";
import { useAsync } from "~/hooks/useAsync";

export function DeleteWorkflowEmailReceiver({
emailReceiverId,
}: {
emailReceiverId: string;
}) {
const [open, setOpen] = useState(false);

const deleteEmailReceiver = useAsync(
EmailReceiverApiService.deleteEmailReceiver,
{
onSuccess: () => {
mutate(EmailReceiverApiService.getEmailReceivers.key());
},
onError: () => toast.error(`Something went wrong.`),
}
);

const handleDelete = async () => {
await deleteEmailReceiver.execute(emailReceiverId);
setOpen(false);
};

return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button variant="ghost" size="icon">
<TrashIcon />
</Button>
</PopoverTrigger>

<PopoverContent>
<p>Are you sure you want to delete this email trigger?</p>
<div className="flex justify-end gap-2">
<Button variant="ghost" onClick={() => setOpen(false)}>
Cancel
</Button>
<Button
variant="destructive"
onClick={handleDelete}
loading={deleteEmailReceiver.isLoading}
>
Delete
</Button>
</div>
</PopoverContent>
</Popover>
);
}
2 changes: 1 addition & 1 deletion ui/admin/app/components/workflow/DeleteWorkflowWebhook.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export function DeleteWorkflowWebhook({ webhookId }: { webhookId: string }) {

return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger>
<PopoverTrigger asChild>
<Button variant="ghost" size="icon">
<TrashIcon />
</Button>
Expand Down
2 changes: 2 additions & 0 deletions ui/admin/app/components/workflow/Workflow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
WorkflowProvider,
useWorkflow,
} from "~/components/workflow/WorkflowContext";
import { WorkflowEmailPanel } from "~/components/workflow/WorkflowEmailPanel";
import { WorkflowSchedulePanel } from "~/components/workflow/WorkflowSchedulePanel";
import { WorkflowWebhookPanel } from "~/components/workflow/WorkflowWebhookPanel";
import { StepsForm } from "~/components/workflow/steps/StepsForm";
Expand Down Expand Up @@ -172,6 +173,7 @@ function WorkflowContent({ className }: WorkflowProps) {

<WorkflowSchedulePanel workflowId={workflow.id} />
<WorkflowWebhookPanel workflowId={workflow.id} />
<WorkflowEmailPanel workflowId={workflow.id} />
</ScrollArea>

<footer className="flex justify-between items-center p-4 gap-4 border-t text-muted-foreground">
Expand Down
Loading

0 comments on commit 0d3b161

Please sign in to comment.