Add per-download Amazon button settings for current and archived downloads

This commit is contained in:
nmemmert
2026-05-07 07:49:02 -04:00
parent 9f0aa8bd65
commit bc3d906e64
4 changed files with 55 additions and 2 deletions
+4
View File
@@ -172,6 +172,8 @@ function sanitizeCustomLinks(value) {
url: sanitizeUrl(item.url),
imageUrl: sanitizeUrl(item.imageUrl),
description: typeof item.description === 'string' ? item.description.trim().slice(0, 4000) : '',
amazonUrl: sanitizeUrl(item.amazonUrl),
amazonLabel: typeof item.amazonLabel === 'string' ? item.amazonLabel.trim().slice(0, 120) : '',
placement,
}
})
@@ -199,6 +201,8 @@ function sanitizeArchivedSeriesResourceLinks(value) {
label: typeof item.label === 'string' ? item.label.trim().slice(0, 120) : '',
description: typeof item.description === 'string' ? item.description.trim().slice(0, 4000) : '',
url: sanitizeUrl(item.url),
amazonUrl: sanitizeUrl(item.amazonUrl),
amazonLabel: typeof item.amazonLabel === 'string' ? item.amazonLabel.trim().slice(0, 120) : '',
}))
.filter(item => item.label && item.url)
}
+33 -1
View File
@@ -993,7 +993,7 @@ export default function AdminPage({ content, onSave, onLogout }: Props) {
...series,
resourceLinks: [
...(series.resourceLinks ?? []),
{ id: `${seriesId}-${Date.now().toString(36)}`, label: '', description: '', url: '' },
{ id: `${seriesId}-${Date.now().toString(36)}`, label: '', description: '', url: '', amazonUrl: '', amazonLabel: '' },
],
}
: series),
@@ -1048,6 +1048,8 @@ export default function AdminPage({ content, onSave, onLogout }: Props) {
label: source.label,
description: source.description ?? '',
url: source.url,
amazonUrl: source.amazonUrl ?? '',
amazonLabel: source.amazonLabel ?? '',
},
],
}
@@ -1075,6 +1077,8 @@ export default function AdminPage({ content, onSave, onLogout }: Props) {
label: link.label,
description: link.description ?? '',
url: link.url,
amazonUrl: link.amazonUrl ?? '',
amazonLabel: link.amazonLabel ?? '',
}))
if (toAdd.length === 0) return series
@@ -1646,6 +1650,14 @@ export default function AdminPage({ content, onSave, onLogout }: Props) {
<label htmlFor={`archive-link-url-${link.id}`}>URL</label>
<input id={`archive-link-url-${link.id}`} type="url" value={link.url} placeholder="https://..." onChange={e => updateArchivedSeriesLink(series.id, link.id, 'url', e.target.value)} />
</div>
<div className="admin-field">
<label htmlFor={`archive-link-amazon-url-${link.id}`}>Amazon URL (optional)</label>
<input id={`archive-link-amazon-url-${link.id}`} type="url" value={link.amazonUrl ?? ''} placeholder="https://amazon.com/..." onChange={e => updateArchivedSeriesLink(series.id, link.id, 'amazonUrl', e.target.value)} />
</div>
<div className="admin-field">
<label htmlFor={`archive-link-amazon-label-${link.id}`}>Amazon Button Label</label>
<input id={`archive-link-amazon-label-${link.id}`} type="text" value={link.amazonLabel ?? ''} placeholder="Get it on Amazon" onChange={e => updateArchivedSeriesLink(series.id, link.id, 'amazonLabel', e.target.value)} />
</div>
</div>
<button type="button" className="btn-admin-remove" onClick={() => removeArchivedSeriesLink(series.id, link.id)}>Remove</button>
</div>
@@ -1724,6 +1736,10 @@ export default function AdminPage({ content, onSave, onLogout }: Props) {
<label htmlFor="resources-study-guide-url">Printed Copy URL (Amazon button)</label>
<input id="resources-study-guide-url" type="url" value={form.studyGuideUrl} placeholder="https://..." onChange={e => handleChange('studyGuideUrl', e.target.value)} />
</div>
<div className="admin-field">
<label htmlFor="resources-study-guide-amazon-label">Amazon Button Label</label>
<input id="resources-study-guide-amazon-label" type="text" value={form.studyGuideAmazonButtonLabel} placeholder="Get it on Amazon" onChange={e => handleChange('studyGuideAmazonButtonLabel', e.target.value)} />
</div>
</div>
</div>
@@ -1756,6 +1772,14 @@ export default function AdminPage({ content, onSave, onLogout }: Props) {
<label htmlFor={`resource-description-${link.id}`}>Description</label>
<textarea id={`resource-description-${link.id}`} rows={3} value={link.description ?? ''} placeholder="Description shown on the download page" onChange={e => updateLink(link.id, 'description', e.target.value)} />
</div>
<div className="admin-field">
<label htmlFor={`resource-amazon-url-${link.id}`}>Amazon URL (optional)</label>
<input id={`resource-amazon-url-${link.id}`} type="url" value={link.amazonUrl ?? ''} placeholder="https://amazon.com/..." onChange={e => updateLink(link.id, 'amazonUrl', e.target.value)} />
</div>
<div className="admin-field">
<label htmlFor={`resource-amazon-label-${link.id}`}>Amazon Button Label</label>
<input id={`resource-amazon-label-${link.id}`} type="text" value={link.amazonLabel ?? ''} placeholder="Get it on Amazon" onChange={e => updateLink(link.id, 'amazonLabel', e.target.value)} />
</div>
<div className="admin-field">
<label htmlFor={`resource-image-${link.id}`}>Image URL</label>
<input id={`resource-image-${link.id}`} type="text" value={link.imageUrl ?? ''} placeholder="/uploads/example.png" onChange={e => updateLink(link.id, 'imageUrl', e.target.value)} />
@@ -1820,6 +1844,14 @@ export default function AdminPage({ content, onSave, onLogout }: Props) {
<label htmlFor={`resources-archive-link-description-${link.id}`}>Description</label>
<textarea id={`resources-archive-link-description-${link.id}`} rows={3} value={link.description ?? ''} placeholder="Description shown on the download page" onChange={e => updateArchivedSeriesLink(series.id, link.id, 'description', e.target.value)} />
</div>
<div className="admin-field">
<label htmlFor={`resources-archive-link-amazon-url-${link.id}`}>Amazon URL (optional)</label>
<input id={`resources-archive-link-amazon-url-${link.id}`} type="url" value={link.amazonUrl ?? ''} placeholder="https://amazon.com/..." onChange={e => updateArchivedSeriesLink(series.id, link.id, 'amazonUrl', e.target.value)} />
</div>
<div className="admin-field">
<label htmlFor={`resources-archive-link-amazon-label-${link.id}`}>Amazon Button Label</label>
<input id={`resources-archive-link-amazon-label-${link.id}`} type="text" value={link.amazonLabel ?? ''} placeholder="Get it on Amazon" onChange={e => updateArchivedSeriesLink(series.id, link.id, 'amazonLabel', e.target.value)} />
</div>
</div>
<button type="button" className="btn-admin-remove" onClick={() => removeArchivedSeriesLink(series.id, link.id)}>Remove</button>
</div>
+12 -1
View File
@@ -281,6 +281,8 @@ interface DownloadPageResource {
tags: string[]
buttonText: string
resourceId: string
amazonUrl?: string
amazonLabel?: string
}
function resolveDownloadPageResource(content: SiteContent, pageId: string | undefined): DownloadPageResource | null {
@@ -299,6 +301,8 @@ function resolveDownloadPageResource(content: SiteContent, pageId: string | unde
tags: resource.tags ?? [],
buttonText: `Download ${resource.label}`,
resourceId: buildCustomResourceDownloadId(resource.id),
amazonUrl: resource.amazonUrl,
amazonLabel: resource.amazonLabel,
}
}
@@ -316,6 +320,8 @@ function resolveDownloadPageResource(content: SiteContent, pageId: string | unde
tags: [],
buttonText: `Download ${link.label || series.title || 'Resource'}`,
resourceId: buildArchivedResourceDownloadId(series.id, link.id),
amazonUrl: link.amazonUrl,
amazonLabel: link.amazonLabel,
}
}
@@ -587,7 +593,7 @@ function StudyGuideSection({ content }: { content: SiteContent }) {
rel="noreferrer"
className="btn-secondary"
>
Get Printed Copy on Amazon
{content.studyGuideAmazonButtonLabel || 'Get it on Amazon'}
</a>
</div>
)}
@@ -819,6 +825,11 @@ function DownloadDetailPage({ content }: { content: SiteContent }) {
</div>
<div className="download-detail-actions">
{resource.amazonUrl && (
<a href={resource.amazonUrl} target="_blank" rel="noreferrer" className="btn-secondary">
{resource.amazonLabel || 'Get it on Amazon'}
</a>
)}
<Link to="/resources" className="btn-secondary">Back to Downloads</Link>
</div>
</div>
+6
View File
@@ -5,6 +5,8 @@ export interface CustomLink {
placement: 'platforms' | 'footer' | 'resources'
imageUrl?: string
description?: string
amazonUrl?: string
amazonLabel?: string
tags?: string[]
}
@@ -27,6 +29,8 @@ export interface ArchivedSeriesResourceLink {
label: string
description?: string
url: string
amazonUrl?: string
amazonLabel?: string
}
export interface ArchivedSeriesNote {
@@ -140,6 +144,7 @@ export interface SiteContent {
studyGuideDescription: string
studyGuideDownloadUrl: string
studyGuideUrl: string
studyGuideAmazonButtonLabel: string
// ── Share ──
shareHeading: string
shareP: string
@@ -250,6 +255,7 @@ export const DEFAULTS: SiteContent = {
'Go deeper in your study with the official Verse by Verse companion guide — now available on Amazon.',
studyGuideDownloadUrl: '',
studyGuideUrl: 'https://a.co/d/01sG2tOJ',
studyGuideAmazonButtonLabel: 'Get it on Amazon',
shareHeading: 'Help one more person hear the Word this week.',
shareP: 'Scan the QR code or text the show link to a friend who needs encouragement today.',
whereToNextEyebrow: 'Where to Next',