diff --git a/expose/api/src/Consumer/Handler/ZippyDownloadRequestHandler.php b/expose/api/src/Consumer/Handler/ZippyDownloadRequestHandler.php index 854e37a9f..9bba02f90 100644 --- a/expose/api/src/Consumer/Handler/ZippyDownloadRequestHandler.php +++ b/expose/api/src/Consumer/Handler/ZippyDownloadRequestHandler.php @@ -5,7 +5,7 @@ namespace App\Consumer\Handler; use Alchemy\CoreBundle\Util\DoctrineUtil; -use Alchemy\NotifyBundle\src\NotifierInterface; +use Alchemy\NotifyBundle\Notification\NotifierInterface; use App\Entity\DownloadRequest; use App\Security\Authentication\JWTManager; use App\ZippyManager; @@ -32,20 +32,22 @@ public function __invoke(ZippyDownloadRequest $message): void // Trigger ZIP preparation $this->zippyManager->getDownloadUrl($downloadRequest->getPublication()); + $daysAvailable = 3; $uri = $this->urlGenerator->generate('archive_download', [ 'id' => $downloadRequest->getPublication()->getId(), ], UrlGeneratorInterface::ABSOLUTE_URL); $downloadUrl = $this->JWTManager->signUri( $uri, - 259200 // 3 days + $daysAvailable * 3600 * 24, ); $this->notifier->sendEmail( $downloadRequest->getEmail(), - 'expose/zippy_download_link', - $downloadRequest->getLocale(), + 'expose-zippy-download-link', [ - 'download_url' => $downloadUrl, + 'locale' => $downloadRequest->getLocale(), + 'downloadUrl' => $downloadUrl, + 'daysAvailable' => $daysAvailable, ] ); } diff --git a/novu/bridge/app/api/novu/route.ts b/novu/bridge/app/api/novu/route.ts index 2cfcb43a8..e728a7444 100644 --- a/novu/bridge/app/api/novu/route.ts +++ b/novu/bridge/app/api/novu/route.ts @@ -1,10 +1,15 @@ import { serve } from "@novu/framework/next"; -import {uploaderCommitAcknowledged, welcomeOnboardingEmail} from "../../novu/workflows"; +import { + exposeDownloadLink, + exposeZippyDownloadLink, + uploaderCommitAcknowledged, +} from "../../novu/workflows"; // the workflows collection can hold as many workflow definitions as you need export const { GET, POST, OPTIONS } = serve({ workflows: [ - welcomeOnboardingEmail, uploaderCommitAcknowledged, + exposeZippyDownloadLink, + exposeDownloadLink, ], }); diff --git a/novu/bridge/app/novu/emails/CTAEmail.tsx b/novu/bridge/app/novu/emails/CTAEmail.tsx new file mode 100644 index 000000000..8616e34be --- /dev/null +++ b/novu/bridge/app/novu/emails/CTAEmail.tsx @@ -0,0 +1,68 @@ +import {Button, Section, Text} from "@react-email/components"; +import React from "react"; +import DefaultEmail, { + createEmailControlSchema, + CreateEmailControlSchemaProps, + styles +} from "@/app/novu/emails/DefaultEmail"; +import {z, ZodDefault, ZodOptional, ZodString} from "zod"; + +type Props = { + introText?: string; + outroText?: string; + linkUrl: string; + linkText: string; +}; + +export default function CTAEmail({ + linkUrl, + linkText, + introText, + outroText, +}: Props) { + return ( + +
+ {introText ? + {introText} + : null} + + + + + {linkUrl} + + + {outroText ? + {outroText} + : null} +
+
+ ); +} + +export type CreateCTAEEmailControlSchemaProps = { + defaultIntroText?: string | undefined; + defaultOutroText?: string | undefined; +} & CreateEmailControlSchemaProps; + +export function createCTAEmailControlSchema({ + defaultIntroText, + defaultOutroText, + ...rest +}: CreateCTAEEmailControlSchemaProps) { + return createEmailControlSchema({ + ...rest, + introText: createOptionalOrNotString(defaultIntroText), + outroText: createOptionalOrNotString(defaultOutroText), + }); +} + +function createOptionalOrNotString(defaultValue: string | undefined): ZodOptional | ZodDefault { + const string = z.string(); + if (defaultValue) { + return string.default(defaultValue); + } else { + return string.optional(); + } +} diff --git a/novu/bridge/app/novu/emails/DefaultEmail.tsx b/novu/bridge/app/novu/emails/DefaultEmail.tsx new file mode 100644 index 000000000..1b2727af0 --- /dev/null +++ b/novu/bridge/app/novu/emails/DefaultEmail.tsx @@ -0,0 +1,80 @@ +import {Body, Container, Head, Html, Preview} from "@react-email/components"; +import React, {CSSProperties, PropsWithChildren} from "react"; +import {z} from "zod"; +import {ZodRawShape} from "zod/lib/types"; + +type Props = PropsWithChildren<{}>; + +export default function DefaultEmail({ + children, +}: Props) { + return ( + + + Dropbox reset your password + + + {children} + + + + ); +} + +const main: CSSProperties = { + backgroundColor: "#abc5ff", + padding: "8px 0", +}; + +const container: CSSProperties = { + backgroundColor: "#ffffff", + border: "1px solid #f0f0f0", + padding: "45px", +}; + +export const text: CSSProperties = { + fontSize: "16px", + fontWeight: "300", + color: "#404040", + lineHeight: "26px", +}; +const headingText: CSSProperties = { + fontWeight: "300", + lineHeight: "30px", +} +const button: CSSProperties = { + backgroundColor: "#007ee6", + borderRadius: "4px", + color: "#fff", + fontSize: "15px", + textDecoration: "none", + textAlign: "center" as const, + display: "block", + width: "210px", + padding: "14px 7px", +}; + +const anchor: CSSProperties = { + textDecoration: "underline", +}; + +export const styles: Record = { + text, + headingText: headingText, + button, + anchor, +} + +export type CreateEmailControlSchemaProps = { + defaultEmailSubject: string; +} & ZodRawShape; + +export function createEmailControlSchema({ + defaultEmailSubject, + ...rest +}: CreateEmailControlSchemaProps) { + return z.object({ + emailSubject: z.string().default(defaultEmailSubject), + ...rest, + }); +} diff --git a/novu/bridge/app/novu/emails/novu-onboarding-email.tsx b/novu/bridge/app/novu/emails/novu-onboarding-email.tsx deleted file mode 100644 index 6dcff5dca..000000000 --- a/novu/bridge/app/novu/emails/novu-onboarding-email.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import React from "react"; -import { - Body, - Button, - CodeInline, - Column, - Container, - Head, - Heading, - Html, - Img, - Preview, - render, - Row, - Section, - Tailwind, - Text, -} from "@react-email/components"; - -import { ControlSchema, PayloadSchema } from "../workflows"; - -type NovuWelcomeEmailProps = ControlSchema & PayloadSchema; - -export const NovuWelcomeEmail = ({ - components, - userImage, - teamImage, - arrowImage, - showHeader, -}: NovuWelcomeEmailProps) => { - return ( - - - Novu Welcome - - - {showHeader ? ( - Novu - ) : null} - - - {components?.map((component, componentIndex) => { - return ( -
- {component.type === "heading" ? ( -
- - {component.text} - -
- ) : null} - - {component.type === "button" ? ( -
- -
- ) : null} - - {component.type === "text" ? ( -
- - {component.text} - -
- ) : null} - - {component.type === "users" ? ( -
- - - {component.text} - - - - - - - - invited you to - - - - - -
- ) : null} - {component.type === "code" ? ( -
- {component.text}; -
- ) : null} -
- ); - })} -
- - - - Powered by Novu, the Code-First Notification Infrastructure - - - -
- - ); -}; - -export default NovuWelcomeEmail; - -export function renderEmail(controls: ControlSchema, payload: PayloadSchema) { - return render(); -} diff --git a/novu/bridge/app/novu/workflows/expose/download-link.ts b/novu/bridge/app/novu/workflows/expose/download-link.tsx similarity index 62% rename from novu/bridge/app/novu/workflows/expose/download-link.ts rename to novu/bridge/app/novu/workflows/expose/download-link.tsx index 32b093aa9..aa13cfdf9 100644 --- a/novu/bridge/app/novu/workflows/expose/download-link.ts +++ b/novu/bridge/app/novu/workflows/expose/download-link.tsx @@ -1,5 +1,7 @@ import {workflow} from "@novu/framework"; import {z} from "zod"; +import {render} from "@react-email/components"; +import CTAEmail from "@/app/novu/emails/CTAEmail"; export const exposeDownloadLink = workflow( "expose-download-link", @@ -7,7 +9,12 @@ export const exposeDownloadLink = workflow( await step.email("Email", async () => { return { subject: `Your download link is ready!`, - body: `You can download your file from the following link: ${payload.downloadUrl}`, + body: render( + ), }; }); }, diff --git a/novu/bridge/app/novu/workflows/expose/index.ts b/novu/bridge/app/novu/workflows/expose/index.ts new file mode 100644 index 000000000..49ddb11f1 --- /dev/null +++ b/novu/bridge/app/novu/workflows/expose/index.ts @@ -0,0 +1,2 @@ +export * from "./zippy-download-link"; +export * from "./download-link"; diff --git a/novu/bridge/app/novu/workflows/expose/zippy-download-link.tsx b/novu/bridge/app/novu/workflows/expose/zippy-download-link.tsx new file mode 100644 index 000000000..8eb9af970 --- /dev/null +++ b/novu/bridge/app/novu/workflows/expose/zippy-download-link.tsx @@ -0,0 +1,38 @@ +import {workflow} from "@novu/framework"; +import {z} from "zod"; +import { + render, +} from "@react-email/components"; +import CTAEmail, {createCTAEmailControlSchema} from "@/app/novu/emails/CTAEmail"; + +export const exposeZippyDownloadLink = workflow( + 'expose-zippy-download-link', + async ({step, payload}) => { + await step.email("Email", async (controls) => { + return { + subject: controls.emailSubject, + body: render( + ), + }; + }, { + controlSchema: createCTAEmailControlSchema({ + defaultEmailSubject: 'Your file is ready!', + defaultIntroText: 'You can download your file from the following link:', + }) + }); + }, + { + payloadSchema: z.object({ + downloadUrl: z + .string() + .describe("The URL link to download the file"), + locale: z + .string() + .describe("The user's locale"), + }) + }, +); diff --git a/novu/bridge/app/novu/workflows/index.ts b/novu/bridge/app/novu/workflows/index.ts index f5f5b1874..f39dabcb0 100644 --- a/novu/bridge/app/novu/workflows/index.ts +++ b/novu/bridge/app/novu/workflows/index.ts @@ -1,3 +1,2 @@ -export * from "./welcome-onboarding-email"; export * from "./uploader/commit-acknowledged"; -export * from "./expose/download-link"; +export * from "./expose"; diff --git a/novu/bridge/app/novu/workflows/welcome-onboarding-email/index.ts b/novu/bridge/app/novu/workflows/welcome-onboarding-email/index.ts deleted file mode 100644 index bdb493600..000000000 --- a/novu/bridge/app/novu/workflows/welcome-onboarding-email/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./schemas"; -export * from "./types"; -export * from "./workflow"; diff --git a/novu/bridge/app/novu/workflows/welcome-onboarding-email/schemas.ts b/novu/bridge/app/novu/workflows/welcome-onboarding-email/schemas.ts deleted file mode 100644 index 34376685b..000000000 --- a/novu/bridge/app/novu/workflows/welcome-onboarding-email/schemas.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { z } from "zod"; - -// Learn more about zod at the official website: https://zod.dev/ -export const payloadSchema = z.object({ - inAppSubject: z - .string() - .describe("The subject of the notification") - .default("**Welcome to Novu!**"), - inAppBody: z - .string() - .describe("The body of the notification") - .default("This is an in-app notification powered by Novu."), - inAppAvatar: z - .string() - .describe("The avatar of the notification") - .default("https://avatars.githubusercontent.com/u/77433905?s=200&v=4"), - teamImage: z - .string() - .url() - .default( - "https://images.spr.so/cdn-cgi/imagedelivery/j42No7y-dcokJuNgXeA0ig/dca73b36-cf39-4e28-9bc7-8a0d0cd8ac70/standalone-gradient2x_2/w=128,quality=90,fit=scale-down", - ), - userImage: z - .string() - .url() - .default( - "https://react-email-demo-48zvx380u-resend.vercel.app/static/vercel-user.png", - ), - arrowImage: z - .string() - .url() - .default( - "https://react-email-demo-bdj5iju9r-resend.vercel.app/static/vercel-arrow.png", - ), -}); - -export const emailControlSchema = z.object({ - subject: z.string().default("A Successful Test on Novu!"), - showHeader: z.boolean().default(true), - components: z - .array( - z.object({ - type: z.enum(["heading", "text", "button", "code", "users"]), - text: z.string().default(""), - align: z.enum(["left", "center", "right"]).default("left"), - }), - ) - .default([ - { - type: "heading", - text: "Welcome to Novu", - align: "center", - }, - { - type: "text", - text: "Congratulations on receiving your first notification email from Novu! Join the hundreds of thousands of developers worldwide who use Novu to build notification platforms for their products.", - align: "left", - }, - { - type: "users", - align: "center", - text: "", - }, - { - type: "text", - text: "Ready to get started? Click on the button below, and you will see first-hand how easily you can edit this email content.", - align: "left", - }, - { - type: "button", - text: "Edit Email", - align: "center", - }, - ]), -}); diff --git a/novu/bridge/app/novu/workflows/welcome-onboarding-email/types.ts b/novu/bridge/app/novu/workflows/welcome-onboarding-email/types.ts deleted file mode 100644 index 2953a6d3f..000000000 --- a/novu/bridge/app/novu/workflows/welcome-onboarding-email/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { z } from "zod"; -import { payloadSchema, emailControlSchema } from "./schemas"; - -export type PayloadSchema = z.infer; -export type ControlSchema = z.infer; diff --git a/novu/bridge/app/novu/workflows/welcome-onboarding-email/workflow.ts b/novu/bridge/app/novu/workflows/welcome-onboarding-email/workflow.ts deleted file mode 100644 index 890632608..000000000 --- a/novu/bridge/app/novu/workflows/welcome-onboarding-email/workflow.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { workflow } from "@novu/framework"; -import { renderEmail } from "../../emails/novu-onboarding-email"; -import { emailControlSchema, payloadSchema } from "./schemas"; - -export const welcomeOnboardingEmail = workflow( - "welcome-onboarding-email", - async ({ step, payload }) => { - await step.inApp("In-App Step", async () => { - return { - subject: payload.inAppSubject, - body: payload.inAppBody, - avatar: payload.inAppAvatar, - }; - }); - }, - { - payloadSchema, - }, -);