Skip to content

Commit 8c471b5

Browse files
authored
Merge pull request #2173 from brefphp/cloud-67
Add a newsletter subscribe page for the Serverless PHP news migration
2 parents ce01ea6 + d54e1aa commit 8c471b5

10 files changed

Lines changed: 161 additions & 20 deletions

File tree

website/content/news.mdx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@ title: News
33
description: "Bref-related news."
44
---
55

6+
import SubscribeForm from '../src/components/news/SubscribeForm';
7+
68
# Latest news
79

8-
<div className="hidden md:block float-right ml-8 pt-16">
9-
<a className="twitter-timeline" data-width="300" data-height="1200" data-dnt="true" href="https://twitter.com/brefphp?ref_src=twsrc%5Etfw">Tweets by brefphp</a>
10-
<script async src="https://platform.twitter.com/widgets.js"></script>
11-
</div>
10+
Get Bref news by email:
11+
12+
<SubscribeForm tag="bref_website" className="mt-2 sm:max-w-xl" />
13+
14+
You can also follow [@brefphp](https://x.com/brefphp) on X.
1215

1316
## [August 2026: osls v4 and zero-downtime assets](/news/04-august-2026)
1417

website/content/news/_meta.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,8 @@ export default {
33
'03-bref-3.0': 'Bref 3.0 is released',
44
'02-bref-2.0': 'Bref 2.0 is released',
55
'01-bref-1.0': 'Bref 1.0 is released',
6+
// Newsletter subscribe landing pages (form + result pages), not articles
7+
subscribe: {
8+
display: 'hidden',
9+
},
610
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
title: Already subscribed
3+
description: "This address is already on the list for the Bref newsletter."
4+
robots: noindex
5+
---
6+
7+
# Already subscribed
8+
9+
This address is already on the list for the Bref newsletter. You do not need to do anything.
10+
11+
The [latest news](/news) is on the website.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
title: You are subscribed
3+
description: "You are subscribed to the Bref newsletter."
4+
robots: noindex
5+
---
6+
7+
# You are subscribed
8+
9+
You are subscribed to the Bref newsletter. Thank you.
10+
11+
The [latest news](/news) is on the website.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
title: Subscribe to the Bref newsletter
3+
description: "The Bref newsletter replaces Serverless PHP news. Subscribe to the Bref newsletter to continue to get the emails."
4+
# Migration landing page for the old Serverless PHP news list, linked from an email only
5+
robots: noindex
6+
---
7+
8+
import SubscribeForm from '../../../src/components/news/SubscribeForm';
9+
10+
# Subscribe to the Bref newsletter
11+
12+
The Bref newsletter replaces Serverless PHP news. The sender is the same person, Matthieu. The sender address is the same, `matthieu@bref.sh`. The Bref newsletter contains product and community updates.
13+
14+
Read the latest issue: [osls v4, and zero-downtime assets](https://bref.mailcoach.app/webview/campaign/20729bf2-7950-4982-95a6-6fde1fb6b33d)
15+
16+
<SubscribeForm tag="from-serverless-php-news" label="Email" />
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
title: Check your inbox
3+
description: "We sent a confirmation email for the Bref newsletter to your address."
4+
robots: noindex
5+
---
6+
7+
# Check your inbox
8+
9+
We sent a confirmation email to your address. Look for it in your inbox and in your spam folder. Click the link in the email to complete the subscription.
10+
11+
If the email does not arrive, return to [/news/subscribe](/news/subscribe) and submit the form again.

website/next-sitemap.config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@
22
module.exports = {
33
siteUrl: process.env.SITE_URL || 'https://bref.sh',
44
generateRobotsTxt: true,
5+
// Newsletter subscribe landing pages: linked from the migration email and Mailcoach redirects only
6+
exclude: ['/news/subscribe', '/news/subscribe/*'],
57
}

website/src/components/home/case-studies.jsx

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import SubscribeForm from '../news/SubscribeForm';
2+
13
const posts = [
24
{
35
id: 2,
@@ -38,20 +40,7 @@ export default function CaseStudies() {
3840
Support & consulting <span aria-hidden="true">&rarr;</span>
3941
</a>
4042
</div>
41-
<form action="https://bref.mailcoach.app/subscribe/be83c960-8fee-43fa-abfa-29669e9f433a"
42-
className="w-full flex flex-col items-start mt-8"
43-
method="post">
44-
<input type="hidden" name="tags" value="bref_website" />
45-
<div className="flex flex-col sm:flex-row w-full sm:w-auto gap-2">
46-
<input
47-
className="min-w-0 flex-auto rounded-md border-0 bg-white/10 px-3.5 py-2 text-white shadow-sm ring-1 ring-inset ring-white/10 placeholder:text-white/75 focus:ring-2 focus:ring-inset focus:ring-white sm:text-sm sm:leading-6"
48-
name="email" aria-label="Email" placeholder="you@example.com" required type="email" />
49-
<button type="submit"
50-
className="flex-none rounded-md !bg-gray-500 hover:!bg-gray-400 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500">
51-
Subscribe to the newsletter
52-
</button>
53-
</div>
54-
</form>
43+
<SubscribeForm tag="bref_website" onDark className="mt-8 sm:w-fit" />
5544
</div>
5645
<div className="mx-auto w-full border-t border-gray-900/10 pt-12 sm:pt-16 lg:mx-0 lg:max-w-none lg:border-t-0 lg:pt-0 text-gray-300">
5746
<div className="-my-4 divide-y divide-gray-900/10">
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
'use client'
2+
3+
import { useEffect, useRef } from 'react'
4+
5+
// Same Mailcoach list as the homepage form (src/components/home/case-studies.jsx).
6+
const MAILCOACH_SUBSCRIBE_URL = 'https://bref.mailcoach.app/subscribe/be83c960-8fee-43fa-abfa-29669e9f433a'
7+
8+
// POST-only subscribe form for the Bref newsletter, used inline on /news and on
9+
// /news/subscribe (the page that moves the old "Serverless PHP news" list to the
10+
// Bref list with an explicit opt-in).
11+
//
12+
// - `tag`: Mailcoach subscriber tag, to measure where subscribers come from.
13+
// - `label`: visible label above the field. Without it the form is a single row
14+
// (placeholder + button).
15+
// - `onDark`: styles for a dark background (the homepage section), instead of
16+
// following the site theme.
17+
// - The migration email links to /news/subscribe?email=... : the address is
18+
// prefilled but nothing is submitted until the reader clicks the button, so
19+
// email prefetchers (which only GET the link) cannot subscribe anyone.
20+
// - Mailcoach sends its double opt-in email and redirects to the bref.sh
21+
// result pages below.
22+
const INPUT_CLASS = {
23+
light: 'text-gray-900 ring-gray-300 placeholder:text-gray-400 focus:ring-blue-500 dark:bg-white/5 dark:text-white dark:ring-white/10 dark:placeholder:text-white/50',
24+
dark: 'bg-white/10 text-white ring-white/10 placeholder:text-white/75 focus:ring-white',
25+
}
26+
const BUTTON_CLASS = {
27+
light: 'bg-blue-500 hover:bg-blue-400',
28+
dark: '!bg-gray-500 hover:!bg-gray-400',
29+
}
30+
31+
export default function SubscribeForm({ tag, label, onDark = false, className = 'my-8' }) {
32+
const variant = onDark ? 'dark' : 'light'
33+
const emailRef = useRef(null)
34+
const honeypotRef = useRef(null)
35+
36+
useEffect(() => {
37+
const email = new URLSearchParams(window.location.search).get('email')
38+
if (email && emailRef.current && !emailRef.current.value) {
39+
// A `+` in the address decodes to a space if the link was not URL-encoded
40+
emailRef.current.value = email.replace(/ /g, '+')
41+
}
42+
}, [])
43+
44+
const onSubmit = (event) => {
45+
// Honeypot: humans never see this field, bots fill it in
46+
if (honeypotRef.current?.value) {
47+
event.preventDefault()
48+
return
49+
}
50+
// Mailcoach stores unknown form fields as subscriber attributes: leave the honeypot out
51+
if (honeypotRef.current) honeypotRef.current.disabled = true
52+
}
53+
54+
return (
55+
<form method="POST" action={MAILCOACH_SUBSCRIBE_URL} onSubmit={onSubmit} className={`not-prose ${className}`}>
56+
<input type="hidden" name="tags" value={tag} />
57+
<input type="hidden" name="redirect_after_subscription_pending" value="https://bref.sh/news/subscribe/pending" />
58+
<input type="hidden" name="redirect_after_already_subscribed" value="https://bref.sh/news/subscribe/already" />
59+
<input type="hidden" name="redirect_after_subscribed" value="https://bref.sh/news/subscribe/done" />
60+
61+
<div className="hidden" aria-hidden="true">
62+
<label htmlFor="subscribe-website">Website</label>
63+
<input ref={honeypotRef} id="subscribe-website" type="text" name="website" tabIndex={-1} autoComplete="off" />
64+
</div>
65+
66+
{label && (
67+
<label htmlFor="subscribe-email" className="block mb-2 text-sm font-semibold text-gray-900 dark:text-gray-100">
68+
{label}
69+
</label>
70+
)}
71+
<div className="flex flex-col sm:flex-row gap-2">
72+
<input
73+
ref={emailRef}
74+
id="subscribe-email"
75+
type="email"
76+
name="email"
77+
required
78+
autoComplete="email"
79+
aria-label={label ? undefined : 'Email'}
80+
placeholder="you@example.com"
81+
className={`min-w-0 flex-auto rounded-md border-0 px-3.5 py-2 shadow-sm ring-1 ring-inset focus:ring-2 focus:ring-inset sm:text-sm sm:leading-6 ${INPUT_CLASS[variant]}`}
82+
/>
83+
<button
84+
type="submit"
85+
className={`flex-none rounded-md px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 ${BUTTON_CLASS[variant]}`}
86+
>
87+
Subscribe to the Bref newsletter
88+
</button>
89+
</div>
90+
</form>
91+
)
92+
}

website/src/lib/site-index.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,10 @@ export async function getDocsSections() {
4343
*/
4444
export async function getNewsPages() {
4545
const pages = (await getPageMap('/news'))
46-
// Skip the /news index page, keep the articles
47-
.filter(item => item.route && item.route !== '/news' && 'frontMatter' in item)
46+
// Keep the articles: their file names start with a number (01-bref-1.0,
47+
// 02-bref-2.0...). This skips the /news index and the newsletter subscribe
48+
// landing pages (/news/subscribe/...).
49+
.filter(item => item.route && /^\/news\/\d+-/.test(item.route) && 'frontMatter' in item)
4850
.map(item => ({
4951
title: item.frontMatter?.title ?? item.title,
5052
route: item.route,

0 commit comments

Comments
 (0)