import { zodResolver } from "@hookform/resolvers/zod"; import { ActionSchemas, EventSchemas, TemplateSchemas } from "@plunk/shared"; import type { Template } from "@prisma/client"; import { AnimatePresence, motion } from "framer-motion"; import { useRouter } from "next/router"; import React, { useEffect, useState } from "react"; import { type FieldError, useForm } from "react-hook-form"; import { toast } from "sonner"; import { CodeBlock, Dropdown, Editor, FullscreenLoader, Modal, MultiselectDropdown, Toggle, Tooltip, } from "../../components"; import { API_URI } from "../../lib/constants"; import { useActions } from "../../lib/hooks/actions"; import { useEvents } from "../../lib/hooks/events"; import { useActiveProject } from "../../lib/hooks/projects"; import { useTemplates } from "../../lib/hooks/templates"; import { useUser } from "../../lib/hooks/users"; import { network } from "../../lib/network"; interface EventValues { event: string; } interface TemplateValues { subject: string; body: string; type: "MARKETING" | "TRANSACTIONAL"; style: "PLUNK" | "HTML"; } interface ActionValues { name: string; runOnce: boolean; delay: number; template: string; events: string[]; notevents: string[]; } /** * */ export default function Index() { const router = useRouter(); const activeProject = useActiveProject(); const { data: user } = useUser(); const { data: events, mutate: eventMutate } = useEvents(); const { data: templates, mutate: templateMutate } = useTemplates(); const { data: actions, mutate: actionMutate } = useActions(); const [eventModal, setEventModal] = useState(false); const [advancedSettings, setAdvancedSettings] = useState(false); const [step, setStep] = useState<0 | 1 | 2 | 3>(0); const [language, setLanguage] = useState<"javascript" | "python" | "curl" | "PHP" | "ruby">("curl"); const [delay, setDelay] = useState<{ delay: number; unit: "MINUTES" | "HOURS" | "DAYS"; }>({ delay: 0, unit: "MINUTES", }); useEffect(() => { switch (delay.unit) { case "MINUTES": actionSetValue("delay", delay.delay); break; case "HOURS": actionSetValue("delay", delay.delay * 60); break; case "DAYS": actionSetValue("delay", delay.delay * 24 * 60); break; } }, [delay]); const { register: eventRegister, handleSubmit: eventHandleSubmit, formState: { errors: eventErrors }, getValues: eventGetValues, } = useForm({ resolver: zodResolver(EventSchemas.post.pick({ event: true })), }); const { register: templateRegister, handleSubmit: templateHandleSubmit, formState: { errors: templateErrors }, setValue: templateSetValue, watch: templateWatch, } = useForm({ resolver: zodResolver(TemplateSchemas.create), defaultValues: { type: "MARKETING", style: "PLUNK", subject: "Welcome to Plunk!", body: '

Welcome to Plunk!

\n' + "

Writing emails in Plunk is super easy, anyone do it! \n" + 'They also support all sorts of cool stuff like code blocks, bold text, and links.

Highlight this text and see what is possible!

', }, }); const { register: actionRegister, handleSubmit: actionHandleSubmit, formState: { errors: actionErrors }, setValue: actionSetValue, watch: actionWatch, } = useForm({ resolver: zodResolver(ActionSchemas.create), defaultValues: { template: "No template selected", events: [], notevents: [], runOnce: false, }, }); if (!activeProject || !events || !user || !actions || !templates) { return ; } const triggerEvent = (data: EventValues) => { toast.promise( network.mock(activeProject.secret, "POST", "/v1/track", { event: data.event, email: user.email, subscribed: true, }), { loading: "Sending your event", success: () => { setEventModal(false); void eventMutate(); return `${data.event} delivered`; }, error: "Having trouble sending your event, we will try again later!", }, ); }; const createTemplate = (data: TemplateValues) => { toast.promise( network.mock(activeProject.secret, "POST", "/v1/templates", { ...data, }), { loading: "Creating new template", success: () => { void templateMutate(); setStep(3); return "Created your template"; }, error: "Could not create new email!", }, ); }; const createAction = (data: ActionValues) => { toast.promise( Promise.all([ fetch("/api/plunk", { method: "POST", body: JSON.stringify({ event: "onboarding-completed", email: user.email, data: { project: activeProject.name, firstEvent: events.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime())[0].name, }, }), headers: { "Content-Type": "application/json" }, }), network.mock(activeProject.secret, "POST", "/v1/actions", { ...data, }), ]), { loading: "Creating new action", success: () => { void actionMutate(); return "Created your action"; }, error: "Could not create new action!", }, ); }; const renderStep = () => { switch (step) { case 0: return ( <> 👋

Let's get started!

Are you ready to give Plunk Actions a spin?

In this 3 step tutorial, we'll help you set up your first email action so that you have an example on hand when you are ready to start building your own.

setStep(1)} className={"mt-6 rounded bg-neutral-800 px-12 py-4 text-sm font-medium text-white"} > Let's get started!
); case 1: return events.length > 0 ? ( <> 🎉

Your event has successfully arrived

We have received your event{" "} {events.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime())[0].name} , you are now ready to create your first email template!

setStep(2)} className={"mt-4 rounded-md bg-neutral-800 px-10 py-3 text-sm font-medium text-white"} > Design an email
) : ( <>

Track your first event

Actions start from events. You can call them whatever you want and send them from anywhere using an API call.

From your application

setLanguage(e as "javascript" | "python" | "curl")} values={[ { value: "curl", name: "cURL" }, { name: "JavaScript", value: "javascript" }, { value: "python", name: "Python" }, { value: "PHP", name: "PHP" }, { value: "ruby", name: "Ruby" }, ]} selectedValue={language} />
'Bearer ${activeProject.secret}', 'Content-Type' => 'application/json'], '{ "event": "my-new-event", "email": "${user.email}" }'); $res = $client->sendAsync($request)->wait();`, ruby: `require "uri" require "json" require "net/http" url = URI("${API_URI}/v1/track") https = Net::HTTP.new(url.host, url.port) https.use_ssl = true request = Net::HTTP::Post.new(url) request["Authorization"] = "Bearer ${activeProject.secret}" request["Content-Type"] = "application/json" request.body = JSON.dump({ "event": "my-new-event", "email": "${user.email}" }) response = https.request(request)`, }[language] } />

From Plunk

setEventModal(true)} className={"mt-6 rounded-md bg-neutral-800 px-10 py-4 text-sm font-medium text-white"} > Trigger a demo event
); case 2: return ( <>

Design an email

Our templates are easy to write and automatically transformed into HTML that email clients understand.

{templateErrors.subject?.message && ( {templateErrors.subject.message} )}
templateSetValue("type", t as "MARKETING" | "TRANSACTIONAL")} values={[ { name: "Marketing", value: "MARKETING" }, { name: "Transactional", value: "TRANSACTIONAL" }, ]} selectedValue={templateWatch("type")} /> {templateErrors.type?.message && ( {templateErrors.type.message} )}
templateSetValue("body", val)} /> {templateErrors.body?.message && ( {templateErrors.body.message} )}
Create
); case 3: return actions.length > 0 ? ( <> 🏎

Your action has been created

Users will now automatically start to receive emails when they complete your event{" "} {events.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime())[0].name} . There is loads more to discover in Plunk but let's try out your action first!

{ toast.promise( network.mock(activeProject.secret, "POST", "/v1/track", { event: eventGetValues("event"), email: user.email, }), { loading: "Sending your event", success: `${eventGetValues("event")} delivered`, error: "Could not deliver your event!", }, ); await router.push("/"); }} > Try it out!
) : ( <>

Creating your first action

Actions tie together events and templates, they automate your email workflows.

{actionErrors.name?.message && ( {actionErrors.name.message} )}
actionSetValue("events", e)} values={events.map((e) => { return { name: e.name, value: e.id }; })} selectedValues={actionWatch("events")} /> {(actionErrors.events as FieldError | undefined)?.message && ( {(actionErrors.events as FieldError | undefined)?.message} )}
actionSetValue("template", t)} values={templates.map((t) => { return { name: t.subject, value: t.id }; })} selectedValue={actionWatch("template")} /> {actionErrors.template?.message && ( {actionErrors.template.message} )}
{
} {advancedSettings && (
setDelay({ ...delay, delay: Number.parseInt(e.target.value), }) } />
setDelay({ ...delay, unit: t as "MINUTES" | "HOURS" | "DAYS", }) } values={[ { name: "Minutes", value: "MINUTES" }, { name: "Hours", value: "HOURS" }, { name: "Days", value: "DAYS" }, ]} selectedValue={delay.unit} />
actionSetValue("runOnce", !actionWatch("runOnce"))} />
)}
Create
); } }; return ( <> setEventModal(!eventModal)} onAction={eventHandleSubmit(triggerEvent)} type={"info"} action={"Trigger"} title={"Trigger an event"} description={"Trigger an event to use in your actions"} >
{eventErrors.event?.message && ( {eventErrors.event.message} )}
{renderStep()}
{step === 0 && (
{ await router.push("/onboarding"); }} > Go back
)}
); }