feat: obfuscate email with click-to-reveal across all pages

Replace all bare info@ occurrences with a click-to-reveal pattern:
- New EmailLink React component (base64 decode on click, never in DOM pre-click)
- privacy, terms, refund pages use EmailLink
- contact/index.html uses a vanilla JS button with the same pattern
- PaymentForm mailto builder uses atob() to keep email out of source literals

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
chris 2026-05-08 10:23:50 -04:00
parent 3330c47af2
commit 57cc5840b9
6 changed files with 63 additions and 10 deletions

View File

@ -1,4 +1,5 @@
import type { Metadata } from 'next'
import EmailLink from '@/components/EmailLink'
export const metadata: Metadata = { title: 'Privacy Policy' }
@ -75,13 +76,12 @@ export default function PrivacyPage() {
<h2>8. Your rights</h2>
<p>
You may request access to, correction of, or deletion of your personal data at any
time by emailing <a href="mailto:info@beachpartyballoons.com">info@beachpartyballoons.com</a>.
time by emailing <EmailLink label="us directly" />.
</p>
<h2>9. Contact</h2>
<p>
Questions about this policy? Email us at{' '}
<a href="mailto:info@beachpartyballoons.com">info@beachpartyballoons.com</a> or call{' '}
Questions about this policy? <EmailLink label="Email us" /> or call{' '}
<a href="tel:+12035551234">(203) 555-1234</a>.
</p>
</div>

View File

@ -1,4 +1,5 @@
import type { Metadata } from 'next'
import EmailLink from '@/components/EmailLink'
export const metadata: Metadata = { title: 'Refund & Cancellation Policy' }
@ -19,8 +20,7 @@ export default function RefundPage() {
<strong>75% store credit</strong>.
</p>
<p>
To cancel, please contact us as soon as possible at{' '}
<a href="mailto:info@beachpartyballoons.com">info@beachpartyballoons.com</a>.
To cancel, please contact us as soon as possible <EmailLink label="email us" />.
</p>
<h2>After Delivery or Pickup</h2>
@ -45,7 +45,7 @@ export default function RefundPage() {
<h2>Questions?</h2>
<p>
Email <a href="mailto:info@beachpartyballoons.com">info@beachpartyballoons.com</a>
<EmailLink label="Email us" />
we&rsquo;re a small local business and we&rsquo;ll always do our best to make things right.
</p>
</div>

View File

@ -1,4 +1,5 @@
import type { Metadata } from 'next'
import EmailLink from '@/components/EmailLink'
export const metadata: Metadata = { title: 'Terms & Conditions' }
@ -32,8 +33,7 @@ export default function TermsPage() {
be resolved in the courts of New Haven County, Connecticut.
</p>
<p>
Questions or concerns? Email us at{' '}
<a href="mailto:info@beachpartyballoons.com">info@beachpartyballoons.com</a>.
Questions or concerns? <EmailLink label="Email us" />.
</p>
<hr />

View File

@ -0,0 +1,42 @@
'use client'
import { useState } from 'react'
const ENCODED = 'aW5mb0BiZWFjaHBhcnR5YmFsbG9vbnMuY29t'
interface Props {
label?: string
subject?: string
body?: string
}
export default function EmailLink({ label = 'click to reveal', subject, body }: Props) {
const [addr, setAddr] = useState<string | null>(null)
if (!addr) {
return (
<button
type="button"
onClick={() => setAddr(atob(ENCODED))}
style={{
background: 'none',
border: 'none',
padding: 0,
color: 'inherit',
cursor: 'pointer',
textDecoration: 'underline',
font: 'inherit',
}}
>
{label}
</button>
)
}
const params: string[] = []
if (subject) params.push(`subject=${encodeURIComponent(subject)}`)
if (body) params.push(`body=${encodeURIComponent(body)}`)
const href = `mailto:${addr}${params.length ? '?' + params.join('&') : ''}`
return <a href={href}>{addr}</a>
}

View File

@ -90,7 +90,8 @@ function buildMailtoLink(payload: CheckoutPayload): string {
`Total: ${fmtCents(payload.grandTotal)}`,
].join('\n')
return `mailto:info@beachpartyballoons.com?subject=${encodeURIComponent('Online Order — Invoice Request')}&body=${encodeURIComponent(body)}`
const addr = atob('aW5mb0BiZWFjaHBhcnR5YmFsbG9vbnMuY29t')
return `mailto:${addr}?subject=${encodeURIComponent('Online Order — Invoice Request')}&body=${encodeURIComponent(body)}`
}
export default function PaymentForm({ payload, onSuccess, onError, active }: Props) {

View File

@ -51,7 +51,17 @@
<div class="is-flex-direction-column is-dark">
<h2 class="is-size-4" style="text-align: center;"> <a target="_blank" href="https://maps.app.goo.gl/gRk6NztgMRUsSVJf9">554 Boston Post Road, Milford, CT 06460</a> </h2>
<h2 class="is-size-4" style="text-align: center;" ><a href="tel:203.283.5626">203.283.5626</a> </h2>
<h2 class="is-size-4" style="text-align: center;" ><a href="mailto:info@beachpartyballoons.com">info@beachpartyballoons.com</a> </h2>
<h2 class="is-size-4" style="text-align: center;" id="bpb-email-h2">
<button id="bpb-email-btn" style="background:none;border:none;cursor:pointer;font:inherit;color:inherit;text-decoration:underline;padding:0;">
Click to show email address
</button>
</h2>
<script>
document.getElementById('bpb-email-btn').addEventListener('click', function () {
var a = atob('aW5mb0BiZWFjaHBhcnR5YmFsbG9vbnMuY29t');
document.getElementById('bpb-email-h2').innerHTML = '<a href="mailto:' + a + '">' + a + '<\/a>';
});
</script>
</div>
</div>