From 7dcd1b17dadf8a580c8a4db7db3f5ac97d50ba28 Mon Sep 17 00:00:00 2001 From: Ben Zhang Date: Sun, 18 Aug 2024 15:43:29 -0700 Subject: [PATCH] Deploy blog email subscription functionality (#3029) This PR deploys https://github.com/WATonomous/mailing-list-gateway and the associated frontend for subscribing to blog updates. - [x] Configure SMTP (perhaps create a `mailing-list-manager@watonomous.ca` alias for the outgoing mail group - [x] Add deployment manifests - [x] Sentry Setup - [x] Add metrics endpoint to Prometheus config - [x] Create frontend. Some examples: - [substack](https://sub.thursdai.news/subscribe) - [couchdb](https://blog.couchdb.org/) - [spotify](https://engineering.atspotify.com/) - can just put it in the footer so that it shows up on every page -- "Sign up for engineering updates" - [stripe](https://stripe.com/blog) - [tailscale](https://tailscale.com/blog#blog-newsletter) - one large centered panel at the bottom image Along with the following, blog infrastructure should be complete: - Comment system: https://github.com/WATonomous/infra-config/pull/2808 - Author profile: https://github.com/WATonomous/infra-config/pull/2893 - Mailing list for blog updates: - https://github.com/WATonomous/infra-config/pull/3024 - This PR --- components/blog-post.tsx | 9 +- components/blog.tsx | 174 +++++++++++++++++++++++++++++++++ components/giscus-comments.tsx | 2 +- components/profile-editor.tsx | 2 +- components/ui/separator.tsx | 2 +- pages/blog.mdx | 59 +---------- styles/global.css | 2 +- 7 files changed, 191 insertions(+), 59 deletions(-) create mode 100644 components/blog.tsx diff --git a/components/blog-post.tsx b/components/blog-post.tsx index c4a8f12..c4584c4 100644 --- a/components/blog-post.tsx +++ b/components/blog-post.tsx @@ -8,6 +8,8 @@ import { Link, useConfig } from "nextra-theme-docs"; import React from 'react'; import Picture from './picture'; import { GithubIcon, LinkIcon, LinkedinIcon, MailIcon, XIcon } from 'lucide-react'; +import { Separator } from './ui/separator'; +import { SubscribeDialog } from './blog'; // Reference for styling: https://github.com/vercel/turbo/blob/22585c9dcc23eb010ab01f177394358af03210d7/docs/pages/blog/turbo-1-10-0.mdx @@ -131,5 +133,10 @@ export function BlogPostHeader() { } export function BlogPostFooter() { - return + return <> + + + + + } diff --git a/components/blog.tsx b/components/blog.tsx new file mode 100644 index 0000000..baad4ad --- /dev/null +++ b/components/blog.tsx @@ -0,0 +1,174 @@ +import { + AlertDialog, + AlertDialogAction, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle +} from "@/components/ui/alert-dialog" +import { + Form, + FormControl, + FormField, + FormItem, + FormMessage +} from "@/components/ui/form" +import { websiteConfig } from '@/lib/data' +import { dayjsTz } from '@/lib/utils' +import { zodResolver } from "@hookform/resolvers/zod" +import { useRouter } from 'next/router' +import { MdxFile } from "nextra" +import { Link } from "nextra-theme-docs" +import { getPagesUnderRoute } from "nextra/context" +import { useState } from "react" +import { useForm } from "react-hook-form" +import { z } from "zod" +import { Button } from "./ui/button" +import { Input } from "./ui/input" + +// Header and Index derived from https://github.com/vercel/turbo/blob/66196a70d02cddc8899ed1423684b1f716aa310e/docs/pages/blog.mdx +export function BlogHeader() { + return ( +
+

+ Breadcrumbs +

+

+ A record of the big and small things happening at WATcloud +

+
+ ); +} + +export function BlogIndex() { + const { locale = websiteConfig.default_locale } = useRouter() + + return getPagesUnderRoute("/blog").map((page) => { + const frontMatter = (page as MdxFile).frontMatter + const { date, timezone } = frontMatter || {} + const dateObj = date && timezone && dayjsTz(date, timezone).toDate() + + return ( +
+ + {page.meta?.title || frontMatter?.title || page.name} + +

+ {frontMatter?.description}{" "} + + {"Read more →"} + +

+ {dateObj ? ( +

+ {/* suppressHydrationWarning is used to prevent warnings due to differing server/client locales */} + +

+ ) : null} +
+ ); + }); +} + +const subscribeFormSchema = z.object({ + email: z.string().email({ + message: "Please enter a valid email.", + }), +}); + +export function SubscribeDialog() { + + function subscribe(e: React.MouseEvent) { + // Add your subscribe logic here + console.log("Subscribed!"); + e.preventDefault(); + } + + const [isAlertOpen, setIsAlertOpen] = useState(false); + const [alertTitle, setAlertTitle] = useState(""); + const [alertDescription, setAlertDescription] = useState(""); + const [isSubmitting, setIsSubmitting] = useState(false); + + const form = useForm>({ + resolver: zodResolver(subscribeFormSchema), + defaultValues: { + email: "", + }, + }); + + async function onSubmit({ email }: z.infer) { + setIsSubmitting(true); + try { + const res = await fetch( + "https://mailing-list-gateway.watonomous.ca/sign-up", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ email, mailing_list: "watcloud-blog-updates@watonomous.ca" }), + } + ); + + if (res.status === 200) { + setAlertTitle("Success!"); + setAlertDescription(`Success! Please check your email inbox to confirm your subscription.`); + form.reset(); + } else { + setAlertTitle("Error"); + setAlertDescription(`Something went wrong! Error code: ${res.status}. Error message: \`${(await res.text())}\`.`); + } + } catch (e) { + setAlertTitle("Error"); + setAlertDescription(`Something went wrong! Network request failed with error "${e}".`); + } + setIsAlertOpen(true); + setIsSubmitting(false); + } + + return ( +
+

{"Subscribe to WATcloud's blog"}

+

{"Get the latest posts delivered right to your inbox. We won't spam you!"}

+
+ + ( + + + + + + {form.formState.errors.email?.message} + + + )} + /> + + + + + + + {alertTitle} + {alertDescription} + + + OK + + + +
+ ); +} diff --git a/components/giscus-comments.tsx b/components/giscus-comments.tsx index baf9ab8..85f786a 100644 --- a/components/giscus-comments.tsx +++ b/components/giscus-comments.tsx @@ -1,6 +1,7 @@ import Giscus from "@giscus/react"; import websiteConfig from "@/build/fixtures/website-config.json"; import { useTheme } from "nextra-theme-docs"; +import { Separator } from "./ui/separator"; export default function CommentSection() { const { theme } = useTheme(); @@ -18,7 +19,6 @@ export default function CommentSection() { return ( <> -
{repo && repo_id && category && category_id ? ( -

- Breadcrumbs -

-

- A record of the big and small things happening at WATcloud -

- - ); -} - -export function BlogIndex() { - return getPagesUnderRoute("/blog").map((page) => { - const { date, timezone } = page.frontMatter || {} - const dateObj = date && timezone && dayjsTz(date, timezone).toDate() - const { locale = websiteConfig.default_locale } = useRouter() - - return ( -
- - {page.meta?.title || page.frontMatter?.title || page.name} - -

- {page.frontMatter?.description}{" "} - - {"Read more →"} - -

- {dateObj ? ( -

- {/* suppressHydrationWarning is used to prevent warnings due to differing server/client locales */} - -

- ) : null} -
- ); - }); -} +import { BlogHeader, BlogIndex, SubscribeDialog } from '@/components/blog' +import { Separator } from "@/components/ui/separator"; - \ No newline at end of file + + + diff --git a/styles/global.css b/styles/global.css index 67846f1..b5a4f1b 100644 --- a/styles/global.css +++ b/styles/global.css @@ -139,5 +139,5 @@ section.footnotes is created by the remark-gfm plugin: - https://github.com/micromark/micromark-extension-gfm-footnote/blob/533e041238c15e7995afbffa7721b0e8d427f68e/readme.md */ section.footnotes { - @apply border-t mt-6; + @apply border-t mt-6 border-gray-400 border-opacity-20; } \ No newline at end of file