Add dedicated subscribe page and seed all JSON defaults on startup
This commit is contained in:
+16
-7
@@ -1,17 +1,26 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
DATA_FILE=/app/data/admin-content.json
|
DATA_DIR=/app/data
|
||||||
SEED_FILE=/app/data-seed/admin-content.json
|
SEED_DIR=/app/data-seed
|
||||||
|
|
||||||
# Ensure the data directory exists (in case the volume was not mounted)
|
# Ensure the data directory exists (in case the volume was not mounted)
|
||||||
mkdir -p /app/data
|
mkdir -p "$DATA_DIR"
|
||||||
|
|
||||||
# Only seed from image defaults when no live data file exists yet.
|
# Seed each default JSON file only when missing.
|
||||||
# This runs on first boot or a fresh volume, but never overwrites existing data.
|
# This runs on first boot or a fresh volume, but never overwrites existing data.
|
||||||
if [ ! -f "$DATA_FILE" ] && [ -f "$SEED_FILE" ]; then
|
if [ -d "$SEED_DIR" ]; then
|
||||||
echo "[siteforge] No admin-content.json found. Seeding from image defaults..."
|
for seed_file in "$SEED_DIR"/*.json; do
|
||||||
cp "$SEED_FILE" "$DATA_FILE"
|
[ -f "$seed_file" ] || continue
|
||||||
|
|
||||||
|
base_name=$(basename "$seed_file")
|
||||||
|
target_file="$DATA_DIR/$base_name"
|
||||||
|
|
||||||
|
if [ ! -f "$target_file" ]; then
|
||||||
|
echo "[siteforge] No $base_name found. Seeding from image defaults..."
|
||||||
|
cp "$seed_file" "$target_file"
|
||||||
|
fi
|
||||||
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exec node server.js
|
exec node server.js
|
||||||
|
|||||||
+14
@@ -774,6 +774,20 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.subscribe-card {
|
||||||
|
max-width: 560px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subscribe-form {
|
||||||
|
margin-top: 1rem;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subscribe-form .btn-secondary {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
.thanks-countdown {
|
.thanks-countdown {
|
||||||
color: #d4b87a !important;
|
color: #d4b87a !important;
|
||||||
letter-spacing: 0.06em;
|
letter-spacing: 0.06em;
|
||||||
|
|||||||
+112
@@ -1127,6 +1127,116 @@ function ThankYouPage() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function SubscribePage() {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const [name, setName] = useState('')
|
||||||
|
const [email, setEmail] = useState('')
|
||||||
|
const [honey, setHoney] = useState('')
|
||||||
|
const [status, setStatus] = useState<'idle' | 'submitting' | 'error'>('idle')
|
||||||
|
const [errorMsg, setErrorMsg] = useState('')
|
||||||
|
|
||||||
|
async function handleSubmit(e: React.FormEvent) {
|
||||||
|
e.preventDefault()
|
||||||
|
setStatus('submitting')
|
||||||
|
setErrorMsg('')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/contact', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
name,
|
||||||
|
email,
|
||||||
|
messageType: 'general',
|
||||||
|
message: 'Newsletter signup from subscribe page',
|
||||||
|
subscribe: true,
|
||||||
|
_honey: honey,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
const data = await res.json().catch(() => ({}))
|
||||||
|
setErrorMsg((data as { message?: string }).message ?? 'Something went wrong. Please try again.')
|
||||||
|
setStatus('error')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
navigate('/subscribe/thanks')
|
||||||
|
} catch {
|
||||||
|
setErrorMsg('Could not connect. Please try again later.')
|
||||||
|
setStatus('error')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="thanks-page" aria-label="Subscribe">
|
||||||
|
<div className="thanks-card subscribe-card">
|
||||||
|
<p className="eyebrow">Verse by Verse with Nate</p>
|
||||||
|
<h1>Subscribe for Updates</h1>
|
||||||
|
<p>
|
||||||
|
Get ministry updates and new episode announcements by email.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form className="contact-form subscribe-form" onSubmit={handleSubmit} noValidate>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="contact-honeypot"
|
||||||
|
tabIndex={-1}
|
||||||
|
autoComplete="off"
|
||||||
|
aria-hidden="true"
|
||||||
|
value={honey}
|
||||||
|
onChange={e => setHoney(e.target.value)}
|
||||||
|
/>
|
||||||
|
<label>
|
||||||
|
Name
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="name"
|
||||||
|
required
|
||||||
|
autoComplete="name"
|
||||||
|
value={name}
|
||||||
|
onChange={e => setName(e.target.value)}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Email
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
name="email"
|
||||||
|
required
|
||||||
|
autoComplete="email"
|
||||||
|
value={email}
|
||||||
|
onChange={e => setEmail(e.target.value)}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
{status === 'error' && <p className="contact-error">{errorMsg}</p>}
|
||||||
|
|
||||||
|
<button type="submit" className="btn-primary" disabled={status === 'submitting'}>
|
||||||
|
{status === 'submitting' ? 'Subscribing…' : 'Subscribe'}
|
||||||
|
</button>
|
||||||
|
<Link to="/" className="btn-secondary">Back to Site</Link>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function SubscribeThankYouPage() {
|
||||||
|
return (
|
||||||
|
<main className="thanks-page" aria-label="Subscription confirmed">
|
||||||
|
<div className="thanks-card">
|
||||||
|
<p className="eyebrow">Verse by Verse with Nate</p>
|
||||||
|
<h1>You are subscribed</h1>
|
||||||
|
<p>
|
||||||
|
Thank you for subscribing. You will receive future ministry updates and episode announcements.
|
||||||
|
</p>
|
||||||
|
<Link to="/" className="btn-primary">Back to Site</Link>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function LegalPage({ title, body }: { title: string; body: string[] }) {
|
function LegalPage({ title, body }: { title: string; body: string[] }) {
|
||||||
return (
|
return (
|
||||||
<main className="thanks-page" aria-label={title}>
|
<main className="thanks-page" aria-label={title}>
|
||||||
@@ -1252,6 +1362,8 @@ export default function App() {
|
|||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<LandingPage content={content} />} />
|
<Route path="/" element={<LandingPage content={content} />} />
|
||||||
<Route path="/thanks" element={<ThankYouPage />} />
|
<Route path="/thanks" element={<ThankYouPage />} />
|
||||||
|
<Route path="/subscribe" element={<SubscribePage />} />
|
||||||
|
<Route path="/subscribe/thanks" element={<SubscribeThankYouPage />} />
|
||||||
<Route path="/admin" element={<AdminShell content={content} onSave={setContent} />} />
|
<Route path="/admin" element={<AdminShell content={content} onSave={setContent} />} />
|
||||||
<Route
|
<Route
|
||||||
path="/privacy"
|
path="/privacy"
|
||||||
|
|||||||
Reference in New Issue
Block a user