"use client"; import { api } from "~/trpc/react"; import { Spinner } from "@usesend/ui/src/spinner"; import { Input } from "@usesend/ui/src/input"; import { Editor } from "@usesend/email-editor"; import { useState } from "react"; import { Template } from "@prisma/client"; import { toast } from "@usesend/ui/src/toaster"; import { useDebouncedCallback } from "use-debounce"; import { formatDistanceToNow } from "date-fns"; import { ArrowLeft } from "lucide-react"; import Link from "next/link"; import { use } from "react"; const IMAGE_SIZE_LIMIT = 10 * 1024 * 1024; export default function EditTemplatePage({ params, }: { params: Promise<{ templateId: string }>; }) { const { templateId } = use(params); const { data: template, isLoading, error, } = api.template.getTemplate.useQuery( { templateId: templateId }, { enabled: !!templateId, }, ); if (isLoading) { return (
); } if (error) { return (

Failed to load template

); } if (!template) { return
Template not found
; } return ; } function TemplateEditor({ template, }: { template: Template & { imageUploadSupported: boolean }; }) { const utils = api.useUtils(); const [json, setJson] = useState | undefined>( template.content ? JSON.parse(template.content) : undefined, ); const [isSaving, setIsSaving] = useState(false); const [name, setName] = useState(template.name); const [subject, setSubject] = useState(template.subject); const updateTemplateMutation = api.template.updateTemplate.useMutation({ onSuccess: () => { utils.template.getTemplate.invalidate(); setIsSaving(false); }, }); const getUploadUrl = api.template.generateImagePresignedUrl.useMutation(); function updateEditorContent() { updateTemplateMutation.mutate({ templateId: template.id, content: JSON.stringify(json), }); } const deboucedUpdateTemplate = useDebouncedCallback( updateEditorContent, 1000, ); const handleFileChange = async (file: File) => { if (file.size > IMAGE_SIZE_LIMIT) { throw new Error( `File should be less than ${IMAGE_SIZE_LIMIT / 1024 / 1024}MB`, ); } console.log("file type: ", file.type); const { uploadUrl, imageUrl } = await getUploadUrl.mutateAsync({ name: file.name, type: file.type, templateId: template.id, }); const response = await fetch(uploadUrl, { method: "PUT", body: file, }); if (!response.ok) { throw new Error("Failed to upload file"); } return imageUrl; }; return (
setName(e.target.value)} className=" border-0 focus:ring-0 focus:outline-none px-0.5 w-[300px]" onBlur={() => { if (name === template.name || !name) { return; } updateTemplateMutation.mutate( { templateId: template.id, name, }, { onError: (e) => { toast.error(`${e.message}. Reverting changes.`); setName(template.name); }, }, ); }} />
{isSaving ? (
) : (
)} {formatDistanceToNow(template.updatedAt) === "less than a minute" ? "just now" : `${formatDistanceToNow(template.updatedAt)} ago`}
{ setSubject(e.target.value); }} onBlur={() => { if (subject === template.subject || !subject) { return; } updateTemplateMutation.mutate( { templateId: template.id, subject, }, { onError: (e) => { toast.error(`${e.message}. Reverting changes.`); setSubject(template.subject); }, }, ); }} className="mt-1 py-1 text-sm block w-full outline-none border-b border-transparent focus:border-border bg-transparent" />
{ setJson(content.getJSON()); setIsSaving(true); deboucedUpdateTemplate(); }} variables={["email", "firstName", "lastName"]} uploadImage={ template.imageUploadSupported ? handleFileChange : undefined } />
); }