Enforce recipient-bound single-use invites and fix issue card layout; clean release tooling
Magent CI/CD / verify (push) Successful in 10m31s
Magent CI/CD / deploy-prod (push) Skipped
Magent CI/CD / deploy-beta (push) Skipped

This commit is contained in:
2026-09-07 19:59:51 +12:00
parent 13edcb8136
commit a3b5759708
19 changed files with 439 additions and 305 deletions
+11 -4
View File
@@ -7,6 +7,7 @@ import { clearToken, getApiBase, setToken } from '../lib/auth'
type InviteInfo = {
code: string
email_bound?: boolean
label?: string | null
description?: string | null
enabled: boolean
@@ -38,14 +39,15 @@ function SignupPageContent() {
const [inviteLoading, setInviteLoading] = useState(false)
const [loading, setLoading] = useState(false)
const [username, setUsername] = useState('')
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [confirmPassword, setConfirmPassword] = useState('')
const [error, setError] = useState<string | null>(null)
const [status, setStatus] = useState<string | null>(null)
const canSubmit = useMemo(() => {
return Boolean(invite?.is_usable && username.trim() && password && !loading)
}, [invite, username, password, loading])
return Boolean(invite?.is_usable && (invite.email_bound || email.trim()) && username.trim() && password && !loading && !inviteLoading)
}, [invite, email, username, password, loading, inviteLoading])
const lookupInvite = async (code: string) => {
const trimmed = code.trim()
@@ -110,6 +112,7 @@ function SignupPageContent() {
body: JSON.stringify({
invite_code: inviteCode,
username: username.trim(),
...(!invite.email_bound ? { email: email.trim() } : {}),
password,
}),
})
@@ -120,7 +123,7 @@ function SignupPageContent() {
const data = await response.json()
if (data?.authenticated) {
setToken('cookie')
window.location.href = '/'
window.location.href = '/welcome'
return
}
throw new Error('Sign-up did not complete')
@@ -140,7 +143,7 @@ function SignupPageContent() {
<div className="invite-lookup-row">
<input
value={inviteCode}
onChange={(e) => setInviteCode(e.target.value)}
onChange={(e) => { setInviteCode(e.target.value); setInvite(null); setEmail('') }}
placeholder="Paste your invite code"
autoCapitalize="characters"
/>
@@ -171,6 +174,10 @@ function SignupPageContent() {
</div></details>
</div>
)}
{invite?.email_bound ? <p className="account-hint">Your account will use the email address this invitation was sent to. This invitation can be used once.</p> : <label>
Email address
<input type="email" required value={email} onChange={(e) => setEmail(e.target.value)} autoComplete="email" placeholder="you@example.com" />
</label>}
<label>
Username
<input