Ask for name and email before sending visitors to Amazon or Apple Books (switchable in admin → Books); v2.29.0
Publish Container / build (push) Successful in 15m46s

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-12 16:51:51 -04:00
1 parent 2c342f14bd
commit 9f52437a07
10 files changed
+277 -11

No files matched your search

+75
View File
@@ -222,6 +222,81 @@ export function register(app) {
}
})
// Collects a visitor's details, then hands back the Amazon / Apple Books link for a guide or book.
// The destination always comes from the saved site content, never from the request.
app.post('/api/store-link', studyDownloadRateLimit, async (req, res) => {
try {
const { source, store, firstName, lastName, email, subscribe, _honey } = req.body ?? {}
if (_honey) { res.json({ ok: true }); return }
if (typeof source !== 'string' || !source || (store !== 'amazon' && store !== 'apple')) {
res.status(400).json({ message: 'That link is not available.' }); return
}
if (!firstName || typeof firstName !== 'string' || firstName.trim().length < 1 || firstName.trim().length > 100) {
res.status(400).json({ message: 'First name is required.' }); return
}
if (!lastName || typeof lastName !== 'string' || lastName.trim().length < 1 || lastName.trim().length > 100) {
res.status(400).json({ message: 'Last name is required.' }); return
}
if (!email || typeof email !== 'string' || !/^[a-zA-Z0-9._%+\-]{1,64}@[a-zA-Z0-9.\-]{1,253}\.[a-zA-Z]{2,}$/.test(email.trim())) {
res.status(400).json({ message: 'A valid email address is required.' }); return
}
const published = await loadSiteContentFile(DATA_FILE)
const content = published?.siteContent ?? {}
const pick = (amazon, apple) => (store === 'amazon' ? amazon : apple)
let label = ''
let url = ''
if (source === 'page:primary-guide') {
label = content.studyGuideTitle
url = pick(content.studyGuideUrl, content.studyGuideAppleBooksUrl)
} else if (source === 'page:second-guide') {
const guide = content.secondSeries?.guide
label = guide?.title
url = pick(guide?.amazonUrl, guide?.appleBooksUrl)
} else if (source.startsWith('page:book-guide--')) {
const id = source.slice('page:book-guide--'.length)
const guide = (Array.isArray(content.finishedBooks) ? content.finishedBooks : []).find(b => b?.id === id)?.guide
label = guide?.title
url = pick(guide?.amazonUrl, guide?.appleBooksUrl)
} else if (source.startsWith('page:custom--')) {
const id = source.slice('page:custom--'.length)
const link = (content.customLinks ?? []).find(l => l?.id === id && l.placement === 'resources')
label = link?.label
url = pick(link?.amazonUrl, link?.appleBooksUrl)
} else if (source.startsWith('book:')) {
const id = source.slice('book:'.length)
const book = (content.customLinks ?? []).find(l => l?.id === id && l.placement === 'books')
label = book?.label
url = pick(book?.amazonUrl || book?.url, book?.appleBooksUrl)
}
const destination = sanitizeUrl(url)
if (!destination) { res.status(404).json({ message: 'That link is not available.' }); return }
const name = `${firstName.trim()} ${lastName.trim()}`.trim()
const trimmedEmail = email.trim()
const wantsSubscribe = subscribe !== false
const storeName = store === 'amazon' ? 'Amazon' : 'Apple Books'
addContactSubmission({
name,
email: trimmedEmail,
message: `Requested ${storeName} link: ${label || source}`,
messageType: 'general',
subscribe: wantsSubscribe,
})
if (wantsSubscribe) await syncContactToResend(name, trimmedEmail)
incrementDownloadCount(`store:${source}:${store}`)
res.json({ ok: true, url: destination })
} catch (err) {
console.error('[store-link] request error:', err)
res.status(500).json({ message: 'Failed to process your request. Please try again.' })
}
})
app.get('/api/admin-download-stats', requireAdminAuth, (_req, res) => {
res.json({ counts: state.downloadCounts })
})