From 7f9b5275f3af1ca24cb41374032fac9dba9f7bde Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Fri, 3 Jan 2025 22:53:15 +1100 Subject: [PATCH 1/9] Add Zapier embed --- apps/page/next.config.js | 2 +- apps/web/jsx-types.d.ts | 5 +++++ apps/web/pages/integrations/zapier.tsx | 24 ++++++++++++++++++------ 3 files changed, 24 insertions(+), 7 deletions(-) create mode 100644 apps/web/jsx-types.d.ts diff --git a/apps/page/next.config.js b/apps/page/next.config.js index 62bd3ad..3fb23c8 100644 --- a/apps/page/next.config.js +++ b/apps/page/next.config.js @@ -2,7 +2,7 @@ const withBundleAnalyzer = require("@next/bundle-analyzer")({}); const ContentSecurityPolicy = ` script-src 'self' 'unsafe-eval' 'unsafe-inline' blob: *; - style-src 'self' data: 'unsafe-inline'; + style-src 'self' data: 'unsafe-inline' cdn.zapier.com; img-src 'self' * data:; font-src 'self'; connect-src 'self' wss: *.supabase.co *.changes.page *.intercom.io *.sentry.io vercel.live; diff --git a/apps/web/jsx-types.d.ts b/apps/web/jsx-types.d.ts new file mode 100644 index 0000000..9a6144d --- /dev/null +++ b/apps/web/jsx-types.d.ts @@ -0,0 +1,5 @@ +declare namespace JSX { + interface IntrinsicElements { + "zapier-workflow": any; + } +} diff --git a/apps/web/pages/integrations/zapier.tsx b/apps/web/pages/integrations/zapier.tsx index 1588873..8a6af43 100644 --- a/apps/web/pages/integrations/zapier.tsx +++ b/apps/web/pages/integrations/zapier.tsx @@ -1,4 +1,5 @@ import { InboxIcon, SparklesIcon } from "@heroicons/react/outline"; +import Head from "next/head"; import Image from "next/image"; import FooterComponent from "../../components/layout/footer.component"; import HeaderComponent from "../../components/layout/header.component"; @@ -6,10 +7,20 @@ import Page from "../../components/layout/page.component"; import zapierGitHub from "../../public/images/zapier/github.png"; import zapierTweet from "../../public/images/zapier/tweet.png"; -export default function Example() { +export default function Zapier() { return (
+ + + +
@@ -118,11 +129,12 @@ export default function Example() {
-

- Zapier lets you connect changes.page with thousands of the most - popular apps, so you can automate your work and have more time - for what matters most—no code required. -

+
From 822e05a13632bcd8445c9a120cddd2078185ca5f Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Fri, 3 Jan 2025 22:58:36 +1100 Subject: [PATCH 2/9] Fix CSP config --- apps/page/next.config.js | 4 ++-- apps/web/next.config.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/page/next.config.js b/apps/page/next.config.js index 3fb23c8..94271f9 100644 --- a/apps/page/next.config.js +++ b/apps/page/next.config.js @@ -2,10 +2,10 @@ const withBundleAnalyzer = require("@next/bundle-analyzer")({}); const ContentSecurityPolicy = ` script-src 'self' 'unsafe-eval' 'unsafe-inline' blob: *; - style-src 'self' data: 'unsafe-inline' cdn.zapier.com; + style-src 'self' data: 'unsafe-inline'; img-src 'self' * data:; font-src 'self'; - connect-src 'self' wss: *.supabase.co *.changes.page *.intercom.io *.sentry.io vercel.live; + connect-src 'self' wss: *.supabase.co *.changes.page *.intercom.io *.sentry.io vercel.live *.zapier.com; report-to default `; diff --git a/apps/web/next.config.js b/apps/web/next.config.js index bdfd0aa..f8d9b2d 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -1,11 +1,11 @@ const withBundleAnalyzer = require("@next/bundle-analyzer")({}); const ContentSecurityPolicy = ` - script-src 'self' 'unsafe-eval' 'unsafe-inline' *; + script-src 'self' 'unsafe-eval' 'unsafe-inline' * cdn.zapier.com; style-src 'self' data: 'unsafe-inline' maxcdn.bootstrapcdn.com cdn.jsdelivr.net; img-src 'self' * data: blob:; font-src 'self' data: maxcdn.bootstrapcdn.com cdn.jsdelivr.net; - connect-src 'self' wss: *.supabase.co *.changes.page manageprompt.com; + connect-src 'self' wss: *.supabase.co *.changes.page manageprompt.com *.zapier.com; worker-src 'self' blob:; report-to default `; From a24dbec869f62ec2a53d15bac313f63d094f3837 Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Fri, 3 Jan 2025 23:00:17 +1100 Subject: [PATCH 3/9] Update CSP --- apps/page/next.config.js | 2 +- apps/web/next.config.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/page/next.config.js b/apps/page/next.config.js index 94271f9..62bd3ad 100644 --- a/apps/page/next.config.js +++ b/apps/page/next.config.js @@ -5,7 +5,7 @@ const ContentSecurityPolicy = ` style-src 'self' data: 'unsafe-inline'; img-src 'self' * data:; font-src 'self'; - connect-src 'self' wss: *.supabase.co *.changes.page *.intercom.io *.sentry.io vercel.live *.zapier.com; + connect-src 'self' wss: *.supabase.co *.changes.page *.intercom.io *.sentry.io vercel.live; report-to default `; diff --git a/apps/web/next.config.js b/apps/web/next.config.js index f8d9b2d..9e637ba 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -1,7 +1,7 @@ const withBundleAnalyzer = require("@next/bundle-analyzer")({}); const ContentSecurityPolicy = ` - script-src 'self' 'unsafe-eval' 'unsafe-inline' * cdn.zapier.com; + script-src 'self' 'unsafe-eval' 'unsafe-inline' *; style-src 'self' data: 'unsafe-inline' maxcdn.bootstrapcdn.com cdn.jsdelivr.net; img-src 'self' * data: blob:; font-src 'self' data: maxcdn.bootstrapcdn.com cdn.jsdelivr.net; From a032807a9005ed1a50fabd64a3ffaa09a6157f7e Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Fri, 3 Jan 2025 23:10:41 +1100 Subject: [PATCH 4/9] Update Zapier UI --- apps/web/next.config.js | 4 ++-- apps/web/pages/integrations/zapier.tsx | 30 +++++++------------------- 2 files changed, 10 insertions(+), 24 deletions(-) diff --git a/apps/web/next.config.js b/apps/web/next.config.js index 9e637ba..6354d90 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -2,10 +2,10 @@ const withBundleAnalyzer = require("@next/bundle-analyzer")({}); const ContentSecurityPolicy = ` script-src 'self' 'unsafe-eval' 'unsafe-inline' *; - style-src 'self' data: 'unsafe-inline' maxcdn.bootstrapcdn.com cdn.jsdelivr.net; + style-src 'self' data: 'unsafe-inline' maxcdn.bootstrapcdn.com cdn.jsdelivr.net cdn.zapier.com; img-src 'self' * data: blob:; font-src 'self' data: maxcdn.bootstrapcdn.com cdn.jsdelivr.net; - connect-src 'self' wss: *.supabase.co *.changes.page manageprompt.com *.zapier.com; + connect-src 'self' wss: *.supabase.co *.changes.page manageprompt.com zapier.com *.zapier.com; worker-src 'self' blob:; report-to default `; diff --git a/apps/web/pages/integrations/zapier.tsx b/apps/web/pages/integrations/zapier.tsx index 8a6af43..e9bc2c5 100644 --- a/apps/web/pages/integrations/zapier.tsx +++ b/apps/web/pages/integrations/zapier.tsx @@ -23,21 +23,18 @@ export default function Zapier() { -
+
-
-

- Connect with Zapier and{" "} - - automate your workflow - - , here are some examples: -

-
+
-
+
@@ -126,17 +123,6 @@ export default function Zapier() {
- -
-
- -
-
From eb2730ab8fe6647406ba99da12b8bf11af8601f5 Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Sat, 4 Jan 2025 00:13:13 +1100 Subject: [PATCH 5/9] Add Zapier embed to landing page --- apps/web/components/marketing/features.tsx | 16 ++++++++++++++++ apps/web/pages/_document.js | 4 ++++ apps/web/pages/integrations/zapier.tsx | 6 +----- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/apps/web/components/marketing/features.tsx b/apps/web/components/marketing/features.tsx index e5652b8..640ff29 100644 --- a/apps/web/components/marketing/features.tsx +++ b/apps/web/components/marketing/features.tsx @@ -9,6 +9,7 @@ import { MailIcon, StarIcon, } from "@heroicons/react/solid"; +import Head from "next/head"; import Image from "next/image"; import { useMemo } from "react"; import appScreenshot from "../../public/images/hero/app-screenshot.png"; @@ -76,6 +77,12 @@ export default function Features() { return (
+ + +

@@ -124,6 +131,15 @@ export default function Features() { ))}

+ +
+ +
); } diff --git a/apps/web/pages/_document.js b/apps/web/pages/_document.js index 750bd81..835eacc 100644 --- a/apps/web/pages/_document.js +++ b/apps/web/pages/_document.js @@ -61,6 +61,10 @@ export default class MyDocument extends Document { media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2)" rel="apple-touch-startup-image" /> +
diff --git a/apps/web/pages/integrations/zapier.tsx b/apps/web/pages/integrations/zapier.tsx index e9bc2c5..50f48e0 100644 --- a/apps/web/pages/integrations/zapier.tsx +++ b/apps/web/pages/integrations/zapier.tsx @@ -16,16 +16,12 @@ export default function Zapier() { type="module" src="https://cdn.zapier.com/packages/partner-sdk/v0/zapier-elements/zapier-elements.esm.js" > -
-
+
Date: Sat, 4 Jan 2025 08:47:07 +1100 Subject: [PATCH 6/9] Update Zapier UI --- .../components/layout/header.component.tsx | 3 +- apps/web/components/marketing/features.tsx | 1 + apps/web/next.config.js | 4 +- apps/web/pages/integrations/zapier.tsx | 109 +++--------------- 4 files changed, 19 insertions(+), 98 deletions(-) diff --git a/apps/web/components/layout/header.component.tsx b/apps/web/components/layout/header.component.tsx index 69b63fa..8660ada 100644 --- a/apps/web/components/layout/header.component.tsx +++ b/apps/web/components/layout/header.component.tsx @@ -27,6 +27,7 @@ export default function HeaderComponent() { if (billingDetails?.has_active_subscription) { return [ { name: "Pages", href: ROUTES.PAGES }, + { name: "Zapier", href: ROUTES.ZAPIER }, { name: "Billing", href: ROUTES.BILLING }, { name: "Support", href: ROUTES.SUPPORT, external: true }, ]; @@ -41,7 +42,7 @@ export default function HeaderComponent() { return [ { name: "Pricing", href: ROUTES.PRICING }, - { name: "Zapier Integration", href: ROUTES.ZAPIER }, + { name: "Automate using Zapier", href: ROUTES.ZAPIER }, { name: "Knowledge base", href: ROUTES.DOCS, external: true }, { name: "Support", href: ROUTES.SUPPORT, external: true }, ]; diff --git a/apps/web/components/marketing/features.tsx b/apps/web/components/marketing/features.tsx index 640ff29..3c8d477 100644 --- a/apps/web/components/marketing/features.tsx +++ b/apps/web/components/marketing/features.tsx @@ -138,6 +138,7 @@ export default function Features() { theme="auto" intro-copy-display="show" guess-zap-display="show" + zap-create-from-scratch-display="show" />
diff --git a/apps/web/next.config.js b/apps/web/next.config.js index 6354d90..ffa2f3a 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -2,10 +2,10 @@ const withBundleAnalyzer = require("@next/bundle-analyzer")({}); const ContentSecurityPolicy = ` script-src 'self' 'unsafe-eval' 'unsafe-inline' *; - style-src 'self' data: 'unsafe-inline' maxcdn.bootstrapcdn.com cdn.jsdelivr.net cdn.zapier.com; + style-src 'self' data: 'unsafe-inline' maxcdn.bootstrapcdn.com cdn.jsdelivr.net cdn.zapier.com fonts.googleapis.com; img-src 'self' * data: blob:; font-src 'self' data: maxcdn.bootstrapcdn.com cdn.jsdelivr.net; - connect-src 'self' wss: *.supabase.co *.changes.page manageprompt.com zapier.com *.zapier.com; + connect-src 'self' wss: *.supabase.co *.changes.page manageprompt.com zapier.com *.zapier.com www.google.com; worker-src 'self' blob:; report-to default `; diff --git a/apps/web/pages/integrations/zapier.tsx b/apps/web/pages/integrations/zapier.tsx index 50f48e0..c9226e8 100644 --- a/apps/web/pages/integrations/zapier.tsx +++ b/apps/web/pages/integrations/zapier.tsx @@ -1,13 +1,19 @@ -import { InboxIcon, SparklesIcon } from "@heroicons/react/outline"; +import { GetServerSidePropsContext } from "next"; import Head from "next/head"; -import Image from "next/image"; import FooterComponent from "../../components/layout/footer.component"; import HeaderComponent from "../../components/layout/header.component"; import Page from "../../components/layout/page.component"; -import zapierGitHub from "../../public/images/zapier/github.png"; -import zapierTweet from "../../public/images/zapier/tweet.png"; +import { getSupabaseServerClient } from "../../utils/supabase/supabase-admin"; -export default function Zapier() { +export async function getServerSideProps(ctx: GetServerSidePropsContext) { + const { user } = await getSupabaseServerClient(ctx); + + return { + props: { email: user.email }, + }; +} + +export default function Zapier({ email }: { email: string }) { return (
@@ -18,106 +24,19 @@ export default function Zapier() { > - +
- -
-
-
-
- - -
-
-

- Tweet when you create a post -

-

- Share your new posts on Twitter so that customers are in - the loop with latest changes in your product. -

- -
-
-
-
-
- Post to Tweet -
-
-
-
-
-
-
-
- - -
-
-

- Sync GitHub releases -

-

- Automatically create a new post in your changes page every - time you publish a new release in GitHub. -

- -
-
-
-
- Copy releases from GitHub -
-
-
From 6d11c3b9245dc4a66d4a51a1e37d151090aa677a Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Sat, 4 Jan 2025 09:06:24 +1100 Subject: [PATCH 7/9] Add welcome email --- apps/web/inngest/email/send-welcome-email.ts | 25 +++++++++ apps/web/pages/api/inngest.ts | 2 + apps/web/pages/api/users/webhook.ts | 57 ++++++++++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 apps/web/inngest/email/send-welcome-email.ts create mode 100644 apps/web/pages/api/users/webhook.ts diff --git a/apps/web/inngest/email/send-welcome-email.ts b/apps/web/inngest/email/send-welcome-email.ts new file mode 100644 index 0000000..9ce40c1 --- /dev/null +++ b/apps/web/inngest/email/send-welcome-email.ts @@ -0,0 +1,25 @@ +import inngestClient from "../../utils/inngest"; +import postmarkClient from "../../utils/postmark"; + +export const sendWelcomeEmail = inngestClient.createFunction( + { name: "Email: Welcome" }, + { event: "email/user.welcome" }, + async ({ event }) => { + const { email, payload } = event.data; + + console.log("Job started", { + email, + payload, + }); + + const result = await postmarkClient.sendEmailWithTemplate({ + MessageStream: "outbound", + From: "hello@changes.page", + To: email, + TemplateAlias: "welcome-user", + TemplateModel: payload, + }); + + return { body: "Job completed", result }; + } +); diff --git a/apps/web/pages/api/inngest.ts b/apps/web/pages/api/inngest.ts index 70bdf87..a7b92e7 100644 --- a/apps/web/pages/api/inngest.ts +++ b/apps/web/pages/api/inngest.ts @@ -2,6 +2,7 @@ import { serve } from "inngest/next"; import { handleSubscriptionChange } from "../../inngest/billing/handle-subscription"; import { reportUsageForStripeInvoice } from "../../inngest/billing/report-pages-usage-invoice"; import { sendConfirmEmailNotification } from "../../inngest/email/send-confirm-email-notification"; +import { sendWelcomeEmail } from "../../inngest/email/send-welcome-email"; import { deleteImagesJob } from "../../inngest/jobs/delete-images"; import { sendPostNotification } from "./../../inngest/email/send-post-notification"; @@ -13,6 +14,7 @@ export default serve("changes-page", [ // Emails sendConfirmEmailNotification, sendPostNotification, + sendWelcomeEmail, // Background Jobs deleteImagesJob, ]); diff --git a/apps/web/pages/api/users/webhook.ts b/apps/web/pages/api/users/webhook.ts new file mode 100644 index 0000000..e5078eb --- /dev/null +++ b/apps/web/pages/api/users/webhook.ts @@ -0,0 +1,57 @@ +import { NextApiRequest, NextApiResponse } from "next"; +import inngestClient from "../../../utils/inngest"; + +const databaseWebhook = async (req: NextApiRequest, res: NextApiResponse) => { + if (req.method === "POST") { + try { + if (req?.headers["x-webhook-key"] !== process.env.SUPABASE_WEBHOOK_KEY) { + return res + .status(400) + .json({ error: { statusCode: 500, message: "Invalid webhook key" } }); + } + + const { type, record, old_record } = req.body; + const user: { + id: string; + email?: string; + raw_user_meta_data?: { + name?: string; + full_name?: string; + }; + } = record || old_record; + + const { id, email } = user; + console.log("Trigger databaseWebhook [Users]: Record:", type, id); + + if (type === "INSERT") { + await inngestClient.send({ + name: "email/user.welcome", + data: { + email, + payload: { + first_name: + user.raw_user_meta_data?.full_name ?? + user.raw_user_meta_data?.name ?? + "there", + }, + }, + user: { + id, + }, + }); + } + + return res.status(200).json({ ok: true }); + } catch (err) { + console.log("Trigger databaseWebhook [Users]: Error:", err); + res + .status(500) + .json({ error: { statusCode: 500, message: err.message } }); + } + } else { + res.setHeader("Allow", "POST,PUT"); + res.status(405).end("Method Not Allowed"); + } +}; + +export default databaseWebhook; From 334d451761ddfaec63b09738f15edf3f933ac83b Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Sat, 4 Jan 2025 09:15:57 +1100 Subject: [PATCH 8/9] Update Zapier theme to dark and improve email prop handling --- apps/web/components/marketing/features.tsx | 2 +- apps/web/pages/integrations/zapier.tsx | 4 ++-- apps/web/utils/useDatabase.ts | 10 +++++++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/apps/web/components/marketing/features.tsx b/apps/web/components/marketing/features.tsx index 3c8d477..a73fadb 100644 --- a/apps/web/components/marketing/features.tsx +++ b/apps/web/components/marketing/features.tsx @@ -135,7 +135,7 @@ export default function Features() {
diff --git a/apps/web/utils/useDatabase.ts b/apps/web/utils/useDatabase.ts index 61fc68d..705f1d1 100644 --- a/apps/web/utils/useDatabase.ts +++ b/apps/web/utils/useDatabase.ts @@ -20,9 +20,13 @@ export const getUserById = async (user_id: string): Promise => { return { ...user, - has_active_subscription: user.pro_gifted ? true : ["trialing", "active"].includes( - (user?.stripe_subscription as unknown as Stripe.Subscription)?.status - ), + has_active_subscription: + user.pro_gifted === true + ? true + : ["trialing", "active"].includes( + (user?.stripe_subscription as unknown as Stripe.Subscription) + ?.status + ), } as unknown as IUser; }; From 5b6a960f3c54019d93a55a7ce65ecf9f95a9f4b3 Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Sat, 4 Jan 2025 09:43:55 +1100 Subject: [PATCH 9/9] Update Content Security Policy to allow fonts from fonts.gstatic.com --- apps/web/next.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/next.config.js b/apps/web/next.config.js index ffa2f3a..adbb4bc 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -4,7 +4,7 @@ const ContentSecurityPolicy = ` script-src 'self' 'unsafe-eval' 'unsafe-inline' *; style-src 'self' data: 'unsafe-inline' maxcdn.bootstrapcdn.com cdn.jsdelivr.net cdn.zapier.com fonts.googleapis.com; img-src 'self' * data: blob:; - font-src 'self' data: maxcdn.bootstrapcdn.com cdn.jsdelivr.net; + font-src 'self' data: maxcdn.bootstrapcdn.com cdn.jsdelivr.net fonts.gstatic.com; connect-src 'self' wss: *.supabase.co *.changes.page manageprompt.com zapier.com *.zapier.com www.google.com; worker-src 'self' blob:; report-to default