mirror of
https://github.com/hanzoai/id.git
synced 2026-10-16 18:38:15 +00:00
fix(auth): use feat's clean LoginForm/SignupForm (getAppLogin via SocialButtons; password+PKCE) — auto-merge had mixed main's appLogin/sendLoginCode API with feat's client
This commit is contained in:
+33
-166
@@ -1,8 +1,6 @@
|
||||
import { useEffect, useState, type FormEvent } from 'react'
|
||||
import { useState, type FormEvent } from 'react'
|
||||
import type { AuthClient } from '../client'
|
||||
import type { AppLoginInfo, LoginResponse } from '../types'
|
||||
import { ProviderButtons } from './ProviderButtons'
|
||||
import { OTPForm } from './OTPForm'
|
||||
import type { LoginResponse } from '../types'
|
||||
|
||||
export interface LoginFormProps {
|
||||
readonly client: AuthClient
|
||||
@@ -17,44 +15,12 @@ export interface LoginFormProps {
|
||||
|
||||
export function LoginForm(props: LoginFormProps) {
|
||||
const { client } = props
|
||||
const [app, setApp] = useState<AppLoginInfo | null>(null)
|
||||
const [mode, setMode] = useState<'password' | 'code'>('password')
|
||||
const [identifier, setIdentifier] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const [codeSent, setCodeSent] = useState(false)
|
||||
const [busy, setBusy] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [notice, setNotice] = useState<string | null>(null)
|
||||
|
||||
// Load the application's providers + sign-in methods so we render exactly
|
||||
// what IAM offers (social buttons, email/SMS code). Best-effort: on failure
|
||||
// we still show password login.
|
||||
useEffect(() => {
|
||||
let alive = true
|
||||
client
|
||||
.appLogin()
|
||||
.then((a) => {
|
||||
if (alive) setApp(a)
|
||||
})
|
||||
.catch(() => {})
|
||||
return () => {
|
||||
alive = false
|
||||
}
|
||||
}, [client])
|
||||
|
||||
const codeEnabled =
|
||||
!!app &&
|
||||
(app.enableCodeSignin ||
|
||||
app.signinMethods.some((m) => m.name === 'Verification code' && m.rule !== 'None'))
|
||||
|
||||
function handleResult(res: LoginResponse) {
|
||||
if (res.error) setError(res.error)
|
||||
else if (res.mfaRequired) props.onMfaRequired?.(res)
|
||||
else if (res.redirectUrl) window.location.href = res.redirectUrl
|
||||
else props.onSuccess?.(res)
|
||||
}
|
||||
|
||||
async function onPasswordSubmit(e: FormEvent) {
|
||||
async function onSubmit(e: FormEvent) {
|
||||
e.preventDefault()
|
||||
setBusy(true)
|
||||
setError(null)
|
||||
@@ -70,26 +36,14 @@ export function LoginForm(props: LoginFormProps) {
|
||||
codeChallenge: props.codeChallenge,
|
||||
codeChallengeMethod: props.codeChallengeMethod,
|
||||
})
|
||||
handleResult(res)
|
||||
} catch (err) {
|
||||
setError(String(err))
|
||||
} finally {
|
||||
setBusy(false)
|
||||
}
|
||||
}
|
||||
|
||||
async function onSendCode(e: FormEvent) {
|
||||
e.preventDefault()
|
||||
setBusy(true)
|
||||
setError(null)
|
||||
setNotice(null)
|
||||
try {
|
||||
const r = await client.sendLoginCode(identifier)
|
||||
if (r.ok) {
|
||||
setCodeSent(true)
|
||||
setNotice(`Code sent to ${identifier}`)
|
||||
if (res.error) {
|
||||
setError(res.error)
|
||||
} else if (res.mfaRequired) {
|
||||
props.onMfaRequired?.(res)
|
||||
} else if (res.redirectUrl) {
|
||||
window.location.href = res.redirectUrl
|
||||
} else {
|
||||
setError(r.error ?? 'Could not send code')
|
||||
props.onSuccess?.(res)
|
||||
}
|
||||
} catch (err) {
|
||||
setError(String(err))
|
||||
@@ -98,117 +52,30 @@ export function LoginForm(props: LoginFormProps) {
|
||||
}
|
||||
}
|
||||
|
||||
async function onVerifyCode(code: string) {
|
||||
setBusy(true)
|
||||
setError(null)
|
||||
try {
|
||||
const res = await client.loginWithCode({
|
||||
dest: identifier,
|
||||
code,
|
||||
clientId: props.clientIdOverride ?? client.tenant.clientId,
|
||||
application: client.tenant.appName,
|
||||
organization: client.tenant.orgId,
|
||||
redirectUri: props.redirectUri,
|
||||
state: props.state,
|
||||
})
|
||||
handleResult(res)
|
||||
} catch (err) {
|
||||
setError(String(err))
|
||||
} finally {
|
||||
setBusy(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="hanzo-id-login">
|
||||
{app && app.providers.length > 0 ? (
|
||||
<ProviderButtons
|
||||
client={client}
|
||||
providers={app.providers}
|
||||
mode="login"
|
||||
redirectUri={props.redirectUri}
|
||||
state={props.state}
|
||||
clientIdOverride={props.clientIdOverride}
|
||||
<form onSubmit={onSubmit} className="hanzo-id-login-form" aria-busy={busy}>
|
||||
<label>
|
||||
<span>Email or username</span>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="username"
|
||||
value={identifier}
|
||||
onChange={(e) => setIdentifier(e.target.value)}
|
||||
required
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{mode === 'password' ? (
|
||||
<form onSubmit={onPasswordSubmit} className="hanzo-id-login-form" aria-busy={busy}>
|
||||
<label>
|
||||
<span>Email or username</span>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="username"
|
||||
value={identifier}
|
||||
onChange={(e) => setIdentifier(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
<span>Password</span>
|
||||
<input
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
{error ? <p role="alert" className="hanzo-id-error">{error}</p> : null}
|
||||
<button type="submit" disabled={busy}>{busy ? 'Signing in…' : 'Sign in'}</button>
|
||||
</form>
|
||||
) : (
|
||||
<div className="hanzo-id-code-login">
|
||||
{!codeSent ? (
|
||||
<form onSubmit={onSendCode} className="hanzo-id-login-form" aria-busy={busy}>
|
||||
<label>
|
||||
<span>Email or phone</span>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="username"
|
||||
placeholder="[email protected] or +1 555 555 5555"
|
||||
value={identifier}
|
||||
onChange={(e) => setIdentifier(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
{error ? <p role="alert" className="hanzo-id-error">{error}</p> : null}
|
||||
<button type="submit" disabled={busy}>{busy ? 'Sending…' : 'Send code'}</button>
|
||||
</form>
|
||||
) : (
|
||||
<>
|
||||
{notice ? <p className="hanzo-id-notice">{notice}</p> : null}
|
||||
<OTPForm channel={identifier.includes('@') ? 'email' : 'sms'} onSubmit={onVerifyCode} />
|
||||
{error ? <p role="alert" className="hanzo-id-error">{error}</p> : null}
|
||||
<button
|
||||
type="button"
|
||||
className="hanzo-id-linkbtn"
|
||||
onClick={() => {
|
||||
setCodeSent(false)
|
||||
setNotice(null)
|
||||
}}
|
||||
>
|
||||
Use a different address
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{codeEnabled ? (
|
||||
<button
|
||||
type="button"
|
||||
className="hanzo-id-linkbtn hanzo-id-toggle-mode"
|
||||
onClick={() => {
|
||||
setMode(mode === 'password' ? 'code' : 'password')
|
||||
setError(null)
|
||||
setNotice(null)
|
||||
setCodeSent(false)
|
||||
}}
|
||||
>
|
||||
{mode === 'password' ? 'Sign in with email or SMS code' : 'Sign in with password'}
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
</label>
|
||||
<label>
|
||||
<span>Password</span>
|
||||
<input
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
{error ? <p role="alert" className="hanzo-id-error">{error}</p> : null}
|
||||
<button type="submit" disabled={busy}>{busy ? 'Signing in…' : 'Sign in'}</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { useEffect, useState, type FormEvent } from 'react'
|
||||
import { useState, type FormEvent } from 'react'
|
||||
import type { AuthClient } from '../client'
|
||||
import type { AppLoginInfo } from '../types'
|
||||
import { ProviderButtons } from './ProviderButtons'
|
||||
|
||||
export interface SignupFormProps {
|
||||
readonly client: AuthClient
|
||||
@@ -11,25 +9,11 @@ export interface SignupFormProps {
|
||||
|
||||
export function SignupForm(props: SignupFormProps) {
|
||||
const { client } = props
|
||||
const [app, setApp] = useState<AppLoginInfo | null>(null)
|
||||
const [email, setEmail] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const [busy, setBusy] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
let alive = true
|
||||
client
|
||||
.appLogin()
|
||||
.then((a) => {
|
||||
if (alive) setApp(a)
|
||||
})
|
||||
.catch(() => {})
|
||||
return () => {
|
||||
alive = false
|
||||
}
|
||||
}, [client])
|
||||
|
||||
async function onSubmit(e: FormEvent) {
|
||||
e.preventDefault()
|
||||
setBusy(true)
|
||||
@@ -54,29 +38,24 @@ export function SignupForm(props: SignupFormProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="hanzo-id-signup">
|
||||
{app && app.providers.length > 0 ? (
|
||||
<ProviderButtons client={client} providers={app.providers} mode="signup" />
|
||||
) : null}
|
||||
<form onSubmit={onSubmit} className="hanzo-id-signup-form" aria-busy={busy}>
|
||||
<label>
|
||||
<span>Email</span>
|
||||
<input type="email" autoComplete="email" value={email} onChange={(e) => setEmail(e.target.value)} required />
|
||||
</label>
|
||||
<label>
|
||||
<span>Password</span>
|
||||
<input
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
minLength={12}
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
{error ? <p role="alert" className="hanzo-id-error">{error}</p> : null}
|
||||
<button type="submit" disabled={busy}>{busy ? 'Creating account…' : 'Create account'}</button>
|
||||
</form>
|
||||
</div>
|
||||
<form onSubmit={onSubmit} className="hanzo-id-signup-form" aria-busy={busy}>
|
||||
<label>
|
||||
<span>Email</span>
|
||||
<input type="email" autoComplete="email" value={email} onChange={(e) => setEmail(e.target.value)} required />
|
||||
</label>
|
||||
<label>
|
||||
<span>Password</span>
|
||||
<input
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
minLength={12}
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
{error ? <p role="alert" className="hanzo-id-error">{error}</p> : null}
|
||||
<button type="submit" disabled={busy}>{busy ? 'Creating account…' : 'Create account'}</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user