Add host welcome/reset emails, email field, and /host URL; v2.0.7

- Host accounts now have an optional email field
- Creating a host with an email sends a welcome email with the portal
  URL, username, and password via Resend
- Resetting a password also emails the new credentials if the host
  has an email on file; admin sees a confirmation either way
- Admin panel shows email column and confirmation messages
- Portal URL changed from /host-upload to /host

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 4.6 committed 2026-09-08 14:01:51 -04:00
1 parent 1e396644a9
commit a12a471c41
5 files changed
+90 -10

No files matched your search

+19 -5
View File
@@ -1101,6 +1101,7 @@ interface UploadHost {
id: string
name: string
username: string
email: string
active: boolean
createdAt: string
}
@@ -1111,13 +1112,16 @@ function NuggetsUploadPortalAdmin() {
const [error, setError] = useState('')
const [addName, setAddName] = useState('')
const [addUsername, setAddUsername] = useState('')
const [addEmail, setAddEmail] = useState('')
const [addPassword, setAddPassword] = useState('')
const [addError, setAddError] = useState('')
const [addSaving, setAddSaving] = useState(false)
const [addEmailSent, setAddEmailSent] = useState(false)
const [resetId, setResetId] = useState<string | null>(null)
const [resetPw, setResetPw] = useState('')
const [resetError, setResetError] = useState('')
const [resetSaving, setResetSaving] = useState(false)
const [resetEmailSent, setResetEmailSent] = useState(false)
function reload() {
setLoading(true)
@@ -1138,11 +1142,12 @@ function NuggetsUploadPortalAdmin() {
const r = await fetch('/api/admin/nuggets-upload/hosts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: addName, username: addUsername, password: addPassword }),
body: JSON.stringify({ name: addName, username: addUsername, email: addEmail, password: addPassword }),
})
const d = await r.json()
if (!r.ok) { setAddError(d.message ?? 'Failed.'); return }
setAddName(''); setAddUsername(''); setAddPassword('')
setAddEmailSent(Boolean(d.emailSent))
setAddName(''); setAddUsername(''); setAddEmail(''); setAddPassword('')
reload()
} finally {
setAddSaving(false)
@@ -1177,6 +1182,7 @@ function NuggetsUploadPortalAdmin() {
})
const d = await r.json()
if (!r.ok) { setResetError(d.message ?? 'Failed.'); return }
setResetEmailSent(Boolean(d.emailSent))
setResetId(null); setResetPw('')
reload()
} finally {
@@ -1190,7 +1196,7 @@ function NuggetsUploadPortalAdmin() {
<h2>Nuggets — Upload Portal</h2>
<p>
Manage host login accounts for the upload portal at{' '}
<a href="/host-upload" target="_blank" rel="noopener noreferrer">/host-upload</a>.
<a href="/host" target="_blank" rel="noopener noreferrer">/host</a>.
Each host gets a username and password to upload episode files to Seafile.
</p>
<p style={{ fontSize: '0.82rem', color: '#888', marginTop: 4 }}>
@@ -1207,6 +1213,7 @@ function NuggetsUploadPortalAdmin() {
<tr style={{ borderBottom: '2px solid var(--color-border, #e5e0d5)' }}>
<th style={{ textAlign: 'left', padding: '6px 8px' }}>Name</th>
<th style={{ textAlign: 'left', padding: '6px 8px' }}>Username</th>
<th style={{ textAlign: 'left', padding: '6px 8px' }}>Email</th>
<th style={{ textAlign: 'left', padding: '6px 8px' }}>Status</th>
<th style={{ padding: '6px 8px' }}></th>
</tr>
@@ -1216,6 +1223,7 @@ function NuggetsUploadPortalAdmin() {
<tr key={h.id} style={{ borderBottom: '1px solid var(--color-border, #e5e0d5)' }}>
<td style={{ padding: '7px 8px' }}>{h.name}</td>
<td style={{ padding: '7px 8px', fontFamily: 'monospace' }}>{h.username}</td>
<td style={{ padding: '7px 8px', fontSize: '0.82rem', color: '#888' }}>{h.email || <em>none</em>}</td>
<td style={{ padding: '7px 8px' }}>
<span style={{ color: h.active ? '#27ae60' : '#888', fontWeight: 600 }}>{h.active ? 'Active' : 'Disabled'}</span>
</td>
@@ -1249,6 +1257,7 @@ function NuggetsUploadPortalAdmin() {
{resetError && <p style={{ color: '#c0392b', marginTop: 6, fontSize: '0.85rem' }}>{resetError}</p>}
</form>
)}
{resetEmailSent && !resetId && <p style={{ color: '#27ae60', fontSize: '0.85rem', marginBottom: 8 }}>✓ New password emailed to host.</p>}
<form onSubmit={addHost}>
<h3 style={{ marginBottom: 10 }}>Add Host Account</h3>
@@ -1257,17 +1266,22 @@ function NuggetsUploadPortalAdmin() {
<label>Display Name</label>
<input type="text" placeholder="Jane Smith" value={addName} onChange={e => setAddName(e.target.value)} required />
</div>
<div className="admin-field" style={{ flex: '1 1 140px' }}>
<div className="admin-field" style={{ flex: '1 1 130px' }}>
<label>Username</label>
<input type="text" placeholder="janesmith" value={addUsername} onChange={e => setAddUsername(e.target.value.toLowerCase())} required />
</div>
<div className="admin-field" style={{ flex: '1 1 160px' }}>
<div className="admin-field" style={{ flex: '1 1 180px' }}>
<label>Email (optional — sends welcome)</label>
<input type="email" placeholder="jane@example.com" value={addEmail} onChange={e => setAddEmail(e.target.value)} />
</div>
<div className="admin-field" style={{ flex: '1 1 150px' }}>
<label>Password (min 8 chars)</label>
<input type="password" value={addPassword} onChange={e => setAddPassword(e.target.value)} required minLength={8} />
</div>
<button className="btn-admin-save" type="submit" disabled={addSaving}>{addSaving ? 'Adding…' : '+ Add Host'}</button>
</div>
{addError && <p style={{ color: '#c0392b', marginTop: 6, fontSize: '0.85rem' }}>{addError}</p>}
{addEmailSent && <p style={{ color: '#27ae60', marginTop: 6, fontSize: '0.85rem' }}>✓ Welcome email sent to host with login details.</p>}
</form>
</section>
)