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 e5652b8..a73fadb 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,16 @@ export default function Features() {
))}
+
+
+
+
);
}
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/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/next.config.js b/apps/web/next.config.js
index bdfd0aa..adbb4bc 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 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;
+ 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
`;
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/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;
diff --git a/apps/web/pages/integrations/zapier.tsx b/apps/web/pages/integrations/zapier.tsx
index 1588873..6bb94f6 100644
--- a/apps/web/pages/integrations/zapier.tsx
+++ b/apps/web/pages/integrations/zapier.tsx
@@ -1,128 +1,41 @@
-import { InboxIcon, SparklesIcon } from "@heroicons/react/outline";
-import Image from "next/image";
+import { GetServerSidePropsContext } from "next";
+import Head from "next/head";
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 Example() {
+export async function getServerSideProps(ctx: GetServerSidePropsContext) {
+ const { user } = await getSupabaseServerClient(ctx);
+
+ return {
+ props: { email: user?.email },
+ };
+}
+
+export default function Zapier({ email }: { email?: string }) {
return (
+
+
+
-
-
+
+
-
-
-
- Connect with Zapier and{" "}
-
- automate your workflow
-
- , here are some examples:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 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.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Sync GitHub releases
-
-
- Automatically create a new post in your changes page every
- time you publish a new release in GitHub.
-
-
-
-
-
-
-
-
-
-
-
- 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.
-
+
+
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;
};