ghost-listmonk-connector/ui/pages/sons/[id].tsx
troneras ff5b39241a first commit
fixed cache

Refactoring from app router to page router

Refactoring from app router to page router

Add authentication with JWT

base ui done

protect the dashboard

added transitions

styling

type trick added

cleanup

Add enable-disable logic

add transactional emails

Improvements on delay and frontend

added description on select

implement auth with magic link

migrate to mariadb

webhook signature working

Adding async processing with queues

Implemented webhook reply functionality

Add son execution logs

update status logic

Add recent activity monitoring to sons

show son performance in dashboard

add readme

implement listmonk connector

listmonk-connector-v1

Create CODE_OF_CONDUCT.md

Create LICENSE
2024-08-21 02:09:24 +02:00

210 lines
6.1 KiB
TypeScript

import { NextPageWithExtras } from "next";
import React, { useEffect } from "react";
import { useRouter } from "next/router";
import { useForm, useFieldArray } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Button } from "@/components/ui/button";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Form } from "@/components/ui/form";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { PlusCircle } from "lucide-react";
import { useSon } from "@/hooks/useSon";
import { useCustomToast } from "@/hooks/useCustomToast";
import { SonDetailsForm } from "@/components/SonDetailsForm";
import { ActionForm } from "@/components/ActionForm";
import { editableSonSchema } from "@/lib/schemas";
import { EditableSon } from "@/lib/types";
import { useSonContext } from "@/contexts/SonContext";
import { useListContext } from "@/contexts/ListContext";
import { useTemplateContext } from "@/contexts/TemplateContext";
function isValidTrigger(trigger: string): trigger is EditableSon["trigger"] {
return [
"member_created",
"member_deleted",
"member_updated",
"post_published",
"post_scheduled",
].includes(trigger);
}
const SonDetailPage: NextPageWithExtras = () => {
const router = useRouter();
const { id } = router.query;
const { son, loading: sonLoading, error: sonError } = useSon(id as string);
const { updateSon, deleteSon } = useSonContext();
const { showToast } = useCustomToast();
const { lists, loading: listsLoading, error: listsError } = useListContext();
const {
templates,
loading: templatesLoading,
error: templatesError,
} = useTemplateContext();
const form = useForm<EditableSon>({
resolver: zodResolver(editableSonSchema),
defaultValues: {
name: "",
trigger: "member_created",
delay: 0,
actions: [],
},
});
const { fields, append, remove } = useFieldArray({
control: form.control,
name: "actions",
});
useEffect(() => {
if (son) {
const { name, trigger, delay, actions } = son;
if (isValidTrigger(trigger)) {
form.reset({
name,
trigger,
delay,
actions: actions.map((action) => ({
type: action.type,
parameters: action.parameters,
})),
});
} else {
console.error(`Invalid trigger value: ${trigger}`);
form.reset({
name,
trigger: "member_created",
delay,
actions: actions.map((action) => ({
type: action.type,
parameters: action.parameters,
})),
});
}
}
}, [son, form]);
if (sonLoading || listsLoading || templatesLoading) {
return (
<div className="flex justify-center items-center h-full">Loading...</div>
);
}
if (sonError || listsError || templatesError) {
return (
<div className="text-red-500">
Error:{" "}
{sonError?.message || listsError?.message || templatesError?.message}
</div>
);
}
if (!son) return <div>Son not found</div>;
const onSubmit = async (data: EditableSon) => {
try {
await updateSon(son.id, data);
showToast("Success", "Son updated successfully");
router.push("/sons");
} catch (error) {
showToast("Error", "Failed to update Son", "destructive");
console.error("Failed to update Son:", error);
}
};
const handleDelete = async () => {
try {
await deleteSon(son.id);
showToast("Success", "Son deleted successfully");
router.push("/sons");
} catch (error) {
showToast("Error", "Failed to delete Son", "destructive");
console.error("Failed to delete Son:", error);
}
};
return (
<div className="flex-1 max-w-3xl mx-auto space-y-4">
<h1 className="text-2xl font-bold">Edit Son: {son.name}</h1>
<p className="text-sm text-gray-500">
Created at: {new Date(son.created_at).toLocaleString()}
</p>
<p className="text-sm text-gray-500">
Last updated: {new Date(son.updated_at).toLocaleString()}
</p>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<SonDetailsForm form={form} />
<Card>
<CardHeader>
<CardTitle>Actions</CardTitle>
</CardHeader>
<CardContent className="space-y-6">
{fields.map((field, index) => (
<ActionForm
key={field.id}
form={form}
index={index}
remove={remove}
lists={lists}
templates={templates}
/>
))}
<Button
type="button"
onClick={() =>
append({ type: "send_transactional_email", parameters: {} })
}
variant="outline"
className="w-full"
>
<PlusCircle className="mr-2 h-4 w-4" /> Add Action
</Button>
</CardContent>
</Card>
<Button type="submit" className="w-full">
Save Changes
</Button>
</form>
</Form>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="destructive">Delete Son</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete the
Son.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={handleDelete}>Delete</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
);
};
SonDetailPage.auth = true;
export default SonDetailPage;