diff --git a/frontend/locales/en.json b/frontend/locales/en.json index d85aeb698..0b41c0771 100644 --- a/frontend/locales/en.json +++ b/frontend/locales/en.json @@ -255,6 +255,7 @@ "or_separator": "Or", "password_confirm_label": "Confirm password", "password_label": "Password", + "passwordless_notice": "This account doesn't need a password. We'll send a verification code to your email address instead.", "terms_of_service": "I agree to the Terms and Conditions", "token_checking": "Checking invite code…", "token_invalid": "This invite code is invalid or has expired", diff --git a/frontend/src/entrypoints/register.tsx b/frontend/src/entrypoints/register.tsx index 41933b95c..68c841a07 100644 --- a/frontend/src/entrypoints/register.tsx +++ b/frontend/src/entrypoints/register.tsx @@ -4,7 +4,7 @@ // Please see LICENSE files in the repository root for full details. import { QueryClient, useQuery } from "@tanstack/react-query"; -import { Form, InlineSpinner } from "@vector-im/compound-web"; +import { Form, InlineSpinner, Text } from "@vector-im/compound-web"; import { Suspense, useCallback, useEffect, useRef, useState } from "react"; import { Trans, useTranslation } from "react-i18next"; import * as v from "valibot"; @@ -129,6 +129,7 @@ const REGISTRATION_TOKEN_QUERY = graphql(` valid username email + passwordless } } `); @@ -138,6 +139,7 @@ type TokenInfo = { valid: boolean; username?: string | null; email?: string | null; + passwordless: boolean; }; /** The settled result of the token lookup, rendered in a live region. */ @@ -606,8 +608,16 @@ const PasswordRegisterForm: React.FC<{ data: Data }> = ({ data }) => { const forcedUsername = tokenInfo?.valid ? tokenInfo.username : undefined; const forcedEmail = tokenInfo?.valid ? tokenInfo.email : undefined; + // A passwordless token skips password creation entirely; the email address is + // used to send a verification code instead + const passwordless = tokenInfo?.valid === true && tokenInfo.passwordless; + + // Show the email field when the server requires one, when the token pins one, + // or when it is needed to send the passwordless verification code const showEmail = - data.features.password_registration_email_required || !!forcedEmail; + data.features.password_registration_email_required || + !!forcedEmail || + passwordless; // With no provider to pick from there is nothing to choose, so the whole form // is shown at once @@ -794,11 +804,19 @@ const PasswordRegisterForm: React.FC<{ data: Data }> = ({ data }) => { )} - + {passwordless ? ( + + {t("frontend.register.passwordless_notice")} + + ) : ( + + )} {data.branding.tos_uri && ( ; -export type RegistrationTokenQuery = { registrationToken: { valid: boolean, username: string | null, email: string | null } | null }; +export type RegistrationTokenQuery = { registrationToken: { valid: boolean, username: string | null, email: string | null, passwordless: boolean } | null }; export type UserProfileQueryVariables = Exact<{ [key: string]: never; }>; @@ -1084,6 +1084,7 @@ export const RegistrationTokenDocument = new TypedDocumentString(` valid username email + passwordless } } `) as unknown as TypedDocumentString;