-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 `[email protected]` 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 <img width="1552" alt="image" src="https://github.com/user-attachments/assets/ff11b084-1b90-4de3-b2e9-ded0cb470732"> Along with the following, blog infrastructure should be complete: - Comment system: WATonomous/infra-config#2808 - Author profile: WATonomous/infra-config#2893 - Mailing list for blog updates: - WATonomous/infra-config#3024 - This PR
- Loading branch information
Showing
7 changed files
with
191 additions
and
59 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ( | ||
<div className="max-w-screen-lg mx-auto pt-4 pb-8 mb-16 border-b border-gray-400 border-opacity-20"> | ||
<h1> | ||
<span className="font-bold leading-tight lg:text-5xl">Breadcrumbs</span> | ||
</h1> | ||
<p className="text-center text-gray-500 dark:text-gray-400 font-space-grotesk"> | ||
A record of the big and small things happening at WATcloud | ||
</p> | ||
</div> | ||
); | ||
} | ||
|
||
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 ( | ||
<div key={page.route} className="mb-10"> | ||
<Link href={page.route} style={{ color: "inherit", textDecoration: "none" }} className="block font-semibold mt-8 text-2xl"> | ||
{page.meta?.title || frontMatter?.title || page.name} | ||
</Link> | ||
<p className="opacity-80" style={{ marginTop: ".5rem" }}> | ||
{frontMatter?.description}{" "} | ||
<span className="inline-block"> | ||
<Link href={page.route}>{"Read more →"}</Link> | ||
</span> | ||
</p> | ||
{dateObj ? ( | ||
<p className="opacity-50 text-sm"> | ||
{/* suppressHydrationWarning is used to prevent warnings due to differing server/client locales */} | ||
<time dateTime={dateObj.toISOString()} suppressHydrationWarning> | ||
{dateObj.toLocaleDateString(locale, { | ||
day: 'numeric', | ||
month: 'long', | ||
year: 'numeric' | ||
})} | ||
</time> | ||
</p> | ||
) : null} | ||
</div> | ||
); | ||
}); | ||
} | ||
|
||
const subscribeFormSchema = z.object({ | ||
email: z.string().email({ | ||
message: "Please enter a valid email.", | ||
}), | ||
}); | ||
|
||
export function SubscribeDialog() { | ||
|
||
function subscribe(e: React.MouseEvent<HTMLButtonElement>) { | ||
// 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<z.infer<typeof subscribeFormSchema>>({ | ||
resolver: zodResolver(subscribeFormSchema), | ||
defaultValues: { | ||
email: "", | ||
}, | ||
}); | ||
|
||
async function onSubmit({ email }: z.infer<typeof subscribeFormSchema>) { | ||
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: "[email protected]" }), | ||
} | ||
); | ||
|
||
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 ( | ||
<div className="py-4 rounded-lg"> | ||
<h2 className="text-lg font-semibold">{"Subscribe to WATcloud's blog"}</h2> | ||
<p className="text-sm mt-1">{"Get the latest posts delivered right to your inbox. We won't spam you!"}</p> | ||
<Form {...form}> | ||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4 mt-4"> | ||
<FormField | ||
control={form.control} | ||
name="email" | ||
render={({ field }) => ( | ||
<FormItem> | ||
<FormControl> | ||
<Input placeholder="Email Address" {...field} /> | ||
</FormControl> | ||
<FormMessage> | ||
{form.formState.errors.email?.message} | ||
</FormMessage> | ||
</FormItem> | ||
)} | ||
/> | ||
<Button className="w-full" type="submit" disabled={isSubmitting}> | ||
{isSubmitting ? <>Submitting...</> : <>Subscribe</>} | ||
</Button> | ||
</form> | ||
</Form> | ||
<AlertDialog open={isAlertOpen} onOpenChange={setIsAlertOpen}> | ||
<AlertDialogContent> | ||
<AlertDialogHeader> | ||
<AlertDialogTitle>{alertTitle}</AlertDialogTitle> | ||
<AlertDialogDescription>{alertDescription}</AlertDialogDescription> | ||
</AlertDialogHeader> | ||
<AlertDialogFooter> | ||
<AlertDialogAction>OK</AlertDialogAction> | ||
</AlertDialogFooter> | ||
</AlertDialogContent> | ||
</AlertDialog> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,56 +1,7 @@ | ||
{/* Derived from https://github.com/vercel/turbo/blob/66196a70d02cddc8899ed1423684b1f716aa310e/docs/pages/blog.mdx */} | ||
import { getPagesUnderRoute } from "nextra/context" | ||
import { Link } from "nextra-theme-docs" | ||
import { useRouter } from 'next/router' | ||
import { websiteConfig } from '@/lib/data' | ||
import { dayjsTz } from '@/lib/utils' | ||
|
||
export function BlogHeader() { | ||
return ( | ||
<div className="max-w-screen-lg mx-auto pt-4 pb-8 mb-16 border-b border-gray-400 border-opacity-20"> | ||
<h1> | ||
<span className="font-bold leading-tight lg:text-5xl">Breadcrumbs</span> | ||
</h1> | ||
<p className="text-center text-gray-500 dark:text-gray-400 font-space-grotesk"> | ||
A record of the big and small things happening at WATcloud | ||
</p> | ||
</div> | ||
); | ||
} | ||
|
||
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 ( | ||
<div key={page.route} className="mb-10"> | ||
<Link href={page.route} style={{ color: "inherit", textDecoration: "none" }} className="block font-semibold mt-8 text-2xl"> | ||
{page.meta?.title || page.frontMatter?.title || page.name} | ||
</Link> | ||
<p className="opacity-80" style={{ marginTop: ".5rem" }}> | ||
{page.frontMatter?.description}{" "} | ||
<span className="inline-block"> | ||
<Link href={page.route}>{"Read more →"}</Link> | ||
</span> | ||
</p> | ||
{dateObj ? ( | ||
<p className="opacity-50 text-sm"> | ||
{/* suppressHydrationWarning is used to prevent warnings due to differing server/client locales */} | ||
<time dateTime={dateObj.toISOString()} suppressHydrationWarning> | ||
{dateObj.toLocaleDateString(locale, { | ||
day: 'numeric', | ||
month: 'long', | ||
year: 'numeric' | ||
})} | ||
</time> | ||
</p> | ||
) : null} | ||
</div> | ||
); | ||
}); | ||
} | ||
import { BlogHeader, BlogIndex, SubscribeDialog } from '@/components/blog' | ||
import { Separator } from "@/components/ui/separator"; | ||
|
||
<BlogHeader /> | ||
<BlogIndex /> | ||
<BlogIndex /> | ||
<Separator className="my-8" /> | ||
<SubscribeDialog /> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters