Enforce recipient-bound single-use invites and fix issue card layout; clean release tooling
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user