mirror of
https://github.com/stack-auth/stack.git
synced 2026-07-20 21:29:36 +08:00
Merge 3c48d810cb into 7ed89a2a9c
This commit is contained in:
commit
43868ad64d
@ -92,6 +92,11 @@ function HostedAuthPageInner(props: {
|
||||
const projectFromHook = app.useProject();
|
||||
const project: AuthProject = props.mockProject ?? projectFromHook;
|
||||
|
||||
// Lifted so the typed email survives switching between the "Email" (magic link) and
|
||||
// "Email & Password" (credential) tabs — the tab content is unmounted when inactive,
|
||||
// so keeping the email here (instead of inside each form) both persists and shares it.
|
||||
const [email, setEmail] = useState("");
|
||||
|
||||
if (props.automaticRedirect && user != null && props.mockProject == null) {
|
||||
return (
|
||||
<Suspense fallback={<HostedAuthLoading fullPage={props.fullPage} />}>
|
||||
@ -165,16 +170,16 @@ function HostedAuthPageInner(props: {
|
||||
<TabsTrigger value="password" className={authTabsTriggerClassName}>Email & Password</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="magic-link" className="focus-visible:outline-none focus-visible:ring-0">
|
||||
<MagicLinkSignIn />
|
||||
<MagicLinkSignIn email={email} onEmailChange={setEmail} />
|
||||
</TabsContent>
|
||||
<TabsContent value="password" className="focus-visible:outline-none focus-visible:ring-0">
|
||||
{props.type === "sign-up" ? <CredentialSignUp noPasswordRepeat={props.noPasswordRepeat} /> : <CredentialSignIn />}
|
||||
{props.type === "sign-up" ? <CredentialSignUp noPasswordRepeat={props.noPasswordRepeat} email={email} onEmailChange={setEmail} /> : <CredentialSignIn email={email} onEmailChange={setEmail} />}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
) : project.config.credentialEnabled ? (
|
||||
props.type === "sign-up" ? <CredentialSignUp noPasswordRepeat={props.noPasswordRepeat} /> : <CredentialSignIn />
|
||||
props.type === "sign-up" ? <CredentialSignUp noPasswordRepeat={props.noPasswordRepeat} email={email} onEmailChange={setEmail} /> : <CredentialSignIn email={email} onEmailChange={setEmail} />
|
||||
) : project.config.magicLinkEnabled ? (
|
||||
<MagicLinkSignIn />
|
||||
<MagicLinkSignIn email={email} onEmailChange={setEmail} />
|
||||
) : !(hasOAuthProviders || hasPasskey) ? (
|
||||
<p className="py-4 text-center text-sm text-destructive">No authentication method enabled.</p>
|
||||
) : null}
|
||||
|
||||
@ -7,9 +7,13 @@ import { Button, Input, Label, PasswordInput } from "~/components/ui";
|
||||
import { FormWarningText } from "../supporting/form-elements";
|
||||
import { isValidEmail } from "../supporting/utils";
|
||||
|
||||
export function CredentialSignIn() {
|
||||
export function CredentialSignIn(props: {
|
||||
email: string,
|
||||
onEmailChange: (email: string) => void,
|
||||
}) {
|
||||
const app = useStackApp();
|
||||
const [email, setEmail] = useState("");
|
||||
const email = props.email;
|
||||
const setEmail = props.onEmailChange;
|
||||
const [password, setPassword] = useState("");
|
||||
const [emailError, setEmailError] = useState<string | null>(null);
|
||||
const [passwordError, setPasswordError] = useState<string | null>(null);
|
||||
@ -52,6 +56,7 @@ export function CredentialSignIn() {
|
||||
id="email"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
autoFocus
|
||||
className="h-10 rounded-xl border-border bg-background"
|
||||
value={email}
|
||||
onChange={(event) => {
|
||||
|
||||
@ -10,9 +10,12 @@ import { isValidEmail } from "../supporting/utils";
|
||||
|
||||
export function CredentialSignUp(props: {
|
||||
noPasswordRepeat?: boolean,
|
||||
email: string,
|
||||
onEmailChange: (email: string) => void,
|
||||
}) {
|
||||
const app = useStackApp();
|
||||
const [email, setEmail] = useState("");
|
||||
const email = props.email;
|
||||
const setEmail = props.onEmailChange;
|
||||
const [password, setPassword] = useState("");
|
||||
const [passwordRepeat, setPasswordRepeat] = useState("");
|
||||
const [emailError, setEmailError] = useState<string | null>(null);
|
||||
@ -67,6 +70,7 @@ export function CredentialSignUp(props: {
|
||||
id="email"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
autoFocus
|
||||
className="h-10 rounded-xl border-border bg-background"
|
||||
value={email}
|
||||
onChange={(event) => {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { KnownErrors } from "@hexclave/shared";
|
||||
import { useStackApp } from "@hexclave/react";
|
||||
import { runAsynchronouslyWithAlert } from "@hexclave/shared/dist/utils/promises";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
import {
|
||||
Button,
|
||||
@ -24,6 +24,13 @@ function MagicLinkOtp(props: {
|
||||
const [otp, setOtp] = useState("");
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const otpInputRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
// Focus the (visually hidden) OTP input on mount so the user can type or paste the
|
||||
// code immediately without having to click on the boxes first.
|
||||
useEffect(() => {
|
||||
otpInputRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (otp.length !== 6 || submitting) {
|
||||
@ -56,6 +63,7 @@ function MagicLinkOtp(props: {
|
||||
<form className="mb-4 flex w-full flex-col items-center">
|
||||
<Typography className="mb-4 text-center text-sm text-muted-foreground">Enter the code from your email</Typography>
|
||||
<InputOTP
|
||||
ref={otpInputRef}
|
||||
maxLength={6}
|
||||
type="text"
|
||||
inputMode="text"
|
||||
@ -79,9 +87,13 @@ function MagicLinkOtp(props: {
|
||||
);
|
||||
}
|
||||
|
||||
export function MagicLinkSignIn() {
|
||||
export function MagicLinkSignIn(props: {
|
||||
email: string,
|
||||
onEmailChange: (email: string) => void,
|
||||
}) {
|
||||
const app = useStackApp();
|
||||
const [email, setEmail] = useState("");
|
||||
const email = props.email;
|
||||
const setEmail = props.onEmailChange;
|
||||
const [emailError, setEmailError] = useState<string | null>(null);
|
||||
const [nonce, setNonce] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@ -130,6 +142,7 @@ export function MagicLinkSignIn() {
|
||||
id="email"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
autoFocus
|
||||
className="h-10 rounded-xl border-border bg-background"
|
||||
value={email}
|
||||
onChange={(event) => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user