diff --git a/frontend/knip.config.ts b/frontend/knip.config.ts index be6e73ebd..5bdb749ee 100644 --- a/frontend/knip.config.ts +++ b/frontend/knip.config.ts @@ -6,7 +6,7 @@ import type { KnipConfig } from "knip"; export default { - entry: ["src/entrypoints/*", "src/routes/*"], + entry: ["src/entrypoints/**", "src/routes/*"], ignore: [ "src/gql/*", "src/routeTree.gen.ts", diff --git a/frontend/locales/cs.json b/frontend/locales/cs.json index 8613ad33e..ee3c49b9c 100644 --- a/frontend/locales/cs.json +++ b/frontend/locales/cs.json @@ -31,6 +31,7 @@ "loading": "Načítání…", "next": "Další", "password": "Heslo", + "password_confirm": "Potvrďte heslo", "previous": "Předchozí", "saved": "Uloženo", "saving": "Ukládání..." diff --git a/frontend/locales/da.json b/frontend/locales/da.json index 0529125ff..a6213bfbf 100644 --- a/frontend/locales/da.json +++ b/frontend/locales/da.json @@ -31,6 +31,7 @@ "loading": "Indlæser...", "next": "Næste", "password": "Adgangskode", + "password_confirm": "Bekræft adgangskode", "previous": "Forrige", "saved": "Gemt", "saving": "Gemmer..." diff --git a/frontend/locales/de.json b/frontend/locales/de.json index 75c8493ed..d8dff1a55 100644 --- a/frontend/locales/de.json +++ b/frontend/locales/de.json @@ -31,6 +31,7 @@ "loading": "Lade …", "next": "Weiter", "password": "Passwort", + "password_confirm": "Passwort wiederholen", "previous": "Zurück", "saved": "Gespeichert", "saving": "Speichern..." diff --git a/frontend/locales/en.json b/frontend/locales/en.json index 731eab2f0..849ff7624 100644 --- a/frontend/locales/en.json +++ b/frontend/locales/en.json @@ -29,6 +29,7 @@ "loading": "Loading…", "next": "Next", "password": "Password", + "password_confirm": "Confirm password", "previous": "Previous", "saved": "Saved", "saving": "Saving…" diff --git a/frontend/locales/et.json b/frontend/locales/et.json index 271e268d8..bdc8b556b 100644 --- a/frontend/locales/et.json +++ b/frontend/locales/et.json @@ -31,6 +31,7 @@ "loading": "Laadime…", "next": "Edasi", "password": "Salasõna", + "password_confirm": "Korda salasõna", "previous": "Tagasi", "saved": "Salvestatud", "saving": "Salvestame…" diff --git a/frontend/locales/fi.json b/frontend/locales/fi.json index 8c11dfe85..9959e911b 100644 --- a/frontend/locales/fi.json +++ b/frontend/locales/fi.json @@ -31,6 +31,7 @@ "loading": "Ladataan…", "next": "Seuraava", "password": "Salasana", + "password_confirm": "Vahvista salasana", "previous": "Edellinen", "saved": "Tallennettu", "saving": "Tallennetaan…" diff --git a/frontend/locales/fr.json b/frontend/locales/fr.json index eb4f0b3f9..9cd441180 100644 --- a/frontend/locales/fr.json +++ b/frontend/locales/fr.json @@ -31,6 +31,7 @@ "loading": "Chargement…", "next": "Suivant", "password": "Mot de passe", + "password_confirm": "Confirmer le mot de passe", "previous": "Précédent", "saved": "Sauvegardé", "saving": "Enregistrement..." diff --git a/frontend/locales/hu.json b/frontend/locales/hu.json index 239558da3..6b961f02d 100644 --- a/frontend/locales/hu.json +++ b/frontend/locales/hu.json @@ -31,6 +31,7 @@ "loading": "Betöltés…", "next": "Következő", "password": "Jelszó", + "password_confirm": "Jelszó megerősítése", "previous": "Előző", "saved": "Mentve", "saving": "Mentés…" diff --git a/frontend/locales/nb-NO.json b/frontend/locales/nb-NO.json index 85470c373..d3a124aed 100644 --- a/frontend/locales/nb-NO.json +++ b/frontend/locales/nb-NO.json @@ -31,6 +31,7 @@ "loading": "Laster inn...", "next": "Neste", "password": "Passord", + "password_confirm": "Bekreft passord", "previous": "Forrige", "saved": "Lagret", "saving": "Lagrer…" diff --git a/frontend/locales/nl.json b/frontend/locales/nl.json index e52e20624..3fa583af9 100644 --- a/frontend/locales/nl.json +++ b/frontend/locales/nl.json @@ -31,6 +31,7 @@ "loading": "Laden...", "next": "Volgende", "password": "Wachtwoord", + "password_confirm": "Bevestig wachtwoord", "previous": "Vorige", "saved": "Opgeslagen", "saving": "Opslaan..." diff --git a/frontend/locales/pl.json b/frontend/locales/pl.json index eac1eeffd..96da60850 100644 --- a/frontend/locales/pl.json +++ b/frontend/locales/pl.json @@ -31,6 +31,7 @@ "loading": "Wczytywanie…", "next": "Dalej", "password": "Hasło", + "password_confirm": "Potwierdź hasło", "previous": "Poprzedni", "saved": "Zapisano", "saving": "Zapisywanie…" diff --git a/frontend/locales/pt.json b/frontend/locales/pt.json index 5503dd642..42b928022 100644 --- a/frontend/locales/pt.json +++ b/frontend/locales/pt.json @@ -31,6 +31,7 @@ "loading": "A carregar...", "next": "Seguinte", "password": "Palavra-passe", + "password_confirm": "Confirmar palavra-passe", "previous": "Anterior", "saved": "Guardado", "saving": "A guardar…" diff --git a/frontend/locales/ru.json b/frontend/locales/ru.json index 743597672..0b7ef6345 100644 --- a/frontend/locales/ru.json +++ b/frontend/locales/ru.json @@ -31,6 +31,7 @@ "loading": "Загрузка…", "next": "Далее", "password": "Пароль", + "password_confirm": "Подтверждение пароля", "previous": "Предыдущий", "saved": "Сохранено", "saving": "Сохранение…" diff --git a/frontend/locales/sv.json b/frontend/locales/sv.json index f14d4e02d..3403914f5 100644 --- a/frontend/locales/sv.json +++ b/frontend/locales/sv.json @@ -31,6 +31,7 @@ "loading": "Laddar …", "next": "Nästa", "password": "Lösenord", + "password_confirm": "Bekräfta lösenordet", "previous": "Föregående", "saved": "Sparat", "saving": "Sparar..." diff --git a/frontend/locales/uk.json b/frontend/locales/uk.json index e15d27af9..6a78823f4 100644 --- a/frontend/locales/uk.json +++ b/frontend/locales/uk.json @@ -31,6 +31,7 @@ "loading": "Завантаження…", "next": "Далі", "password": "Пароль", + "password_confirm": "Підтвердити пароль", "previous": "Назад", "saved": "Збережено", "saving": "Збереження..." diff --git a/frontend/locales/zh-Hans.json b/frontend/locales/zh-Hans.json index ec5b8534f..bb1691d6a 100644 --- a/frontend/locales/zh-Hans.json +++ b/frontend/locales/zh-Hans.json @@ -31,6 +31,7 @@ "loading": "加载中...", "next": "下一页", "password": "密码", + "password_confirm": "确认密码", "previous": "上一页", "saved": "已保存", "saving": "正在保存..." diff --git a/frontend/src/components/PasswordCreationDoubleInput.tsx b/frontend/src/components/PasswordCreationDoubleInput.tsx index 6724463c5..066b09e25 100644 --- a/frontend/src/components/PasswordCreationDoubleInput.tsx +++ b/frontend/src/components/PasswordCreationDoubleInput.tsx @@ -52,18 +52,38 @@ const usePasswordComplexity = (password: string): PasswordComplexity => { return result; }; +type PasswordVariant = "register" | "change"; + export default function PasswordCreationDoubleInput({ siteConfig, forceShowNewPasswordInvalid, + variant = "change", }: { siteConfig: FragmentType; forceShowNewPasswordInvalid: boolean; + variant?: PasswordVariant; }): React.ReactElement { const { t } = useTranslation(); const { minimumPasswordComplexity } = useFragment( CONFIG_FRAGMENT, siteConfig, ); + const variantFields = { + register: { + passwordFieldName: "password", + passwordLabel: t("common.password"), + passwordConfirmFieldName: "password_confirm", + passwordConfirmLabel: t("common.password_confirm"), + }, + change: { + passwordFieldName: "new_password", + passwordLabel: t("frontend.password_change.new_password_label"), + passwordConfirmFieldName: "new_password_again", + passwordConfirmLabel: t( + "frontend.password_change.new_password_again_label", + ), + }, + }[variant]; const newPasswordRef = useRef(null); const newPasswordAgainRef = useRef(null); @@ -81,10 +101,8 @@ export default function PasswordCreationDoubleInput({ return ( <> - - - {t("frontend.password_change.new_password_label")} - + + {variantFields.passwordLabel} - + {/* TODO This field has validation defects, some caused by Radix-UI upstream bugs. https://github.com/matrix-org/matrix-authentication-service/issues/2855 */} - - {t("frontend.password_change.new_password_again_label")} - + {variantFields.passwordConfirmLabel} - v !== form.get("new_password")}> + v !== form.get(variantFields.passwordFieldName)} + > {t("frontend.password_change.passwords_no_match")} diff --git a/frontend/src/entrypoints/register/PasswordDoubleInput.tsx b/frontend/src/entrypoints/register/PasswordDoubleInput.tsx new file mode 100644 index 000000000..61b72646d --- /dev/null +++ b/frontend/src/entrypoints/register/PasswordDoubleInput.tsx @@ -0,0 +1,90 @@ +import { + QueryClient, + QueryClientProvider, + queryOptions, + useSuspenseQuery, +} from "@tanstack/react-query"; +import { Form, TooltipProvider } from "@vector-im/compound-web"; +import { StrictMode, Suspense } from "react"; +import ReactDOM from "react-dom/client"; +import { I18nextProvider } from "react-i18next"; +import ErrorBoundary from "../../components/ErrorBoundary"; +import PasswordCreationDoubleInput from "../../components/PasswordCreationDoubleInput"; +import { graphql } from "../../gql"; +import { graphqlRequest } from "../../graphql"; +import i18n, { setupI18n } from "../../i18n"; +import "../shared.css"; + +setupI18n(); + +const HTML_CONTAINER_ID = "password-double-input"; + +const QUERY = graphql(/* GraphQL */ ` + query PasswordChange { + viewer { + __typename + ... on Node { + id + } + } + + siteConfig { + ...PasswordCreationDoubleInput_siteConfig + } + } +`); + +const query = queryOptions({ + queryKey: ["passwordChange"], + queryFn: ({ signal }) => graphqlRequest({ query: QUERY, signal }), +}); + +function PasswordDoubleInput() { + const { + data: { siteConfig }, + } = useSuspenseQuery(query); + + return ( + +
+ +
+
+ ); +} + +function mountComponentWithProviders(containerId: string) { + try { + const el = document.getElementById(containerId); + if (!el) throw new Error(`can not find ${containerId} in DOM`); + + const queryClient = new QueryClient(); + + ReactDOM.createRoot(el).render( + + + + + {`Loading... ${containerId}…`}}> + + + + + + + + , + ); + } catch (err) { + console.error( + `Cannot mount component PasswordCreationDoubleInput on ${containerId}:`, + err, + ); + } +} + +mountComponentWithProviders(HTML_CONTAINER_ID); diff --git a/frontend/src/gql/gql.ts b/frontend/src/gql/gql.ts index 635e117ca..af2ae93d6 100644 --- a/frontend/src/gql/gql.ts +++ b/frontend/src/gql/gql.ts @@ -49,6 +49,7 @@ type Documents = { "\n fragment UserEmailList_user on User {\n hasPassword\n }\n": typeof types.UserEmailList_UserFragmentDoc, "\n fragment UserEmailList_siteConfig on SiteConfig {\n emailChangeAllowed\n passwordLoginEnabled\n }\n": typeof types.UserEmailList_SiteConfigFragmentDoc, "\n fragment BrowserSessionsOverview_user on User {\n id\n\n browserSessions(first: 0, state: ACTIVE) {\n totalCount\n }\n }\n": typeof types.BrowserSessionsOverview_UserFragmentDoc, + "\n query PasswordChange {\n viewer {\n __typename\n ... on Node {\n id\n }\n }\n\n siteConfig {\n ...PasswordCreationDoubleInput_siteConfig\n }\n }\n": typeof types.PasswordChangeDocument, "\n query UserProfile {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n user {\n ...AddEmailForm_user\n ...UserEmailList_user\n ...AccountDeleteButton_user\n hasPassword\n emails(first: 0) {\n totalCount\n }\n }\n }\n }\n\n siteConfig {\n emailChangeAllowed\n passwordLoginEnabled\n accountDeactivationAllowed\n ...AddEmailForm_siteConfig\n ...UserEmailList_siteConfig\n ...PasswordChange_siteConfig\n ...AccountDeleteButton_siteConfig\n }\n }\n": typeof types.UserProfileDocument, "\n query PlanManagementTab {\n siteConfig {\n planManagementIframeUri\n }\n }\n": typeof types.PlanManagementTabDocument, "\n query BrowserSessionList(\n $first: Int\n $after: String\n $last: Int\n $before: String\n $lastActive: DateFilter\n ) {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n\n user {\n id\n\n browserSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n lastActive: $lastActive\n state: ACTIVE\n ) {\n totalCount\n\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n }\n }\n": typeof types.BrowserSessionListDocument, @@ -62,7 +63,6 @@ type Documents = { "\n mutation DoVerifyEmail($id: ID!, $code: String!) {\n completeEmailAuthentication(input: { id: $id, code: $code }) {\n status\n }\n }\n": typeof types.DoVerifyEmailDocument, "\n mutation ResendEmailAuthenticationCode($id: ID!, $language: String!) {\n resendEmailAuthenticationCode(input: { id: $id, language: $language }) {\n status\n }\n }\n": typeof types.ResendEmailAuthenticationCodeDocument, "\n mutation ChangePassword(\n $userId: ID!\n $oldPassword: String!\n $newPassword: String!\n ) {\n setPassword(\n input: {\n userId: $userId\n currentPassword: $oldPassword\n newPassword: $newPassword\n }\n ) {\n status\n }\n }\n": typeof types.ChangePasswordDocument, - "\n query PasswordChange {\n viewer {\n __typename\n ... on Node {\n id\n }\n }\n\n siteConfig {\n ...PasswordCreationDoubleInput_siteConfig\n }\n }\n": typeof types.PasswordChangeDocument, "\n mutation RecoverPassword($ticket: String!, $newPassword: String!) {\n setPasswordByRecovery(\n input: { ticket: $ticket, newPassword: $newPassword }\n ) {\n status\n }\n }\n": typeof types.RecoverPasswordDocument, "\n mutation ResendRecoveryEmail($ticket: String!) {\n resendRecoveryEmail(input: { ticket: $ticket }) {\n status\n progressUrl\n }\n }\n": typeof types.ResendRecoveryEmailDocument, "\n fragment RecoverPassword_userRecoveryTicket on UserRecoveryTicket {\n username\n email\n }\n": typeof types.RecoverPassword_UserRecoveryTicketFragmentDoc, @@ -106,6 +106,7 @@ const documents: Documents = { "\n fragment UserEmailList_user on User {\n hasPassword\n }\n": types.UserEmailList_UserFragmentDoc, "\n fragment UserEmailList_siteConfig on SiteConfig {\n emailChangeAllowed\n passwordLoginEnabled\n }\n": types.UserEmailList_SiteConfigFragmentDoc, "\n fragment BrowserSessionsOverview_user on User {\n id\n\n browserSessions(first: 0, state: ACTIVE) {\n totalCount\n }\n }\n": types.BrowserSessionsOverview_UserFragmentDoc, + "\n query PasswordChange {\n viewer {\n __typename\n ... on Node {\n id\n }\n }\n\n siteConfig {\n ...PasswordCreationDoubleInput_siteConfig\n }\n }\n": types.PasswordChangeDocument, "\n query UserProfile {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n user {\n ...AddEmailForm_user\n ...UserEmailList_user\n ...AccountDeleteButton_user\n hasPassword\n emails(first: 0) {\n totalCount\n }\n }\n }\n }\n\n siteConfig {\n emailChangeAllowed\n passwordLoginEnabled\n accountDeactivationAllowed\n ...AddEmailForm_siteConfig\n ...UserEmailList_siteConfig\n ...PasswordChange_siteConfig\n ...AccountDeleteButton_siteConfig\n }\n }\n": types.UserProfileDocument, "\n query PlanManagementTab {\n siteConfig {\n planManagementIframeUri\n }\n }\n": types.PlanManagementTabDocument, "\n query BrowserSessionList(\n $first: Int\n $after: String\n $last: Int\n $before: String\n $lastActive: DateFilter\n ) {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n\n user {\n id\n\n browserSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n lastActive: $lastActive\n state: ACTIVE\n ) {\n totalCount\n\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n }\n }\n": types.BrowserSessionListDocument, @@ -119,7 +120,6 @@ const documents: Documents = { "\n mutation DoVerifyEmail($id: ID!, $code: String!) {\n completeEmailAuthentication(input: { id: $id, code: $code }) {\n status\n }\n }\n": types.DoVerifyEmailDocument, "\n mutation ResendEmailAuthenticationCode($id: ID!, $language: String!) {\n resendEmailAuthenticationCode(input: { id: $id, language: $language }) {\n status\n }\n }\n": types.ResendEmailAuthenticationCodeDocument, "\n mutation ChangePassword(\n $userId: ID!\n $oldPassword: String!\n $newPassword: String!\n ) {\n setPassword(\n input: {\n userId: $userId\n currentPassword: $oldPassword\n newPassword: $newPassword\n }\n ) {\n status\n }\n }\n": types.ChangePasswordDocument, - "\n query PasswordChange {\n viewer {\n __typename\n ... on Node {\n id\n }\n }\n\n siteConfig {\n ...PasswordCreationDoubleInput_siteConfig\n }\n }\n": types.PasswordChangeDocument, "\n mutation RecoverPassword($ticket: String!, $newPassword: String!) {\n setPasswordByRecovery(\n input: { ticket: $ticket, newPassword: $newPassword }\n ) {\n status\n }\n }\n": types.RecoverPasswordDocument, "\n mutation ResendRecoveryEmail($ticket: String!) {\n resendRecoveryEmail(input: { ticket: $ticket }) {\n status\n progressUrl\n }\n }\n": types.ResendRecoveryEmailDocument, "\n fragment RecoverPassword_userRecoveryTicket on UserRecoveryTicket {\n username\n email\n }\n": types.RecoverPassword_UserRecoveryTicketFragmentDoc, @@ -265,6 +265,10 @@ export function graphql(source: "\n fragment UserEmailList_siteConfig on SiteCo * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql(source: "\n fragment BrowserSessionsOverview_user on User {\n id\n\n browserSessions(first: 0, state: ACTIVE) {\n totalCount\n }\n }\n"): typeof import('./graphql').BrowserSessionsOverview_UserFragmentDoc; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n query PasswordChange {\n viewer {\n __typename\n ... on Node {\n id\n }\n }\n\n siteConfig {\n ...PasswordCreationDoubleInput_siteConfig\n }\n }\n"): typeof import('./graphql').PasswordChangeDocument; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -317,10 +321,6 @@ export function graphql(source: "\n mutation ResendEmailAuthenticationCode($id: * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql(source: "\n mutation ChangePassword(\n $userId: ID!\n $oldPassword: String!\n $newPassword: String!\n ) {\n setPassword(\n input: {\n userId: $userId\n currentPassword: $oldPassword\n newPassword: $newPassword\n }\n ) {\n status\n }\n }\n"): typeof import('./graphql').ChangePasswordDocument; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n query PasswordChange {\n viewer {\n __typename\n ... on Node {\n id\n }\n }\n\n siteConfig {\n ...PasswordCreationDoubleInput_siteConfig\n }\n }\n"): typeof import('./graphql').PasswordChangeDocument; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/frontend/src/gql/graphql.ts b/frontend/src/gql/graphql.ts index 583ebfc5d..92a12c3c8 100644 --- a/frontend/src/gql/graphql.ts +++ b/frontend/src/gql/graphql.ts @@ -1852,6 +1852,17 @@ export type UserEmailList_SiteConfigFragment = { __typename?: 'SiteConfig', emai export type BrowserSessionsOverview_UserFragment = { __typename?: 'User', id: string, browserSessions: { __typename?: 'BrowserSessionConnection', totalCount: number } } & { ' $fragmentName'?: 'BrowserSessionsOverview_UserFragment' }; +export type PasswordChangeQueryVariables = Exact<{ [key: string]: never; }>; + + +export type PasswordChangeQuery = { __typename?: 'Query', viewer: + | { __typename: 'Anonymous', id: string } + | { __typename: 'User', id: string } + , siteConfig: ( + { __typename?: 'SiteConfig' } + & { ' $fragmentRefs'?: { 'PasswordCreationDoubleInput_SiteConfigFragment': PasswordCreationDoubleInput_SiteConfigFragment } } + ) }; + export type UserProfileQueryVariables = Exact<{ [key: string]: never; }>; @@ -1999,17 +2010,6 @@ export type ChangePasswordMutationVariables = Exact<{ export type ChangePasswordMutation = { __typename?: 'Mutation', setPassword: { __typename?: 'SetPasswordPayload', status: SetPasswordStatus } }; -export type PasswordChangeQueryVariables = Exact<{ [key: string]: never; }>; - - -export type PasswordChangeQuery = { __typename?: 'Query', viewer: - | { __typename: 'Anonymous', id: string } - | { __typename: 'User', id: string } - , siteConfig: ( - { __typename?: 'SiteConfig' } - & { ' $fragmentRefs'?: { 'PasswordCreationDoubleInput_SiteConfigFragment': PasswordCreationDoubleInput_SiteConfigFragment } } - ) }; - export type RecoverPasswordMutationVariables = Exact<{ ticket: Scalars['String']['input']; newPassword: Scalars['String']['input']; @@ -2580,6 +2580,22 @@ export const UserEmailListDocument = new TypedDocumentString(` id email }`) as unknown as TypedDocumentString; +export const PasswordChangeDocument = new TypedDocumentString(` + query PasswordChange { + viewer { + __typename + ... on Node { + id + } + } + siteConfig { + ...PasswordCreationDoubleInput_siteConfig + } +} + fragment PasswordCreationDoubleInput_siteConfig on SiteConfig { + id + minimumPasswordComplexity +}`) as unknown as TypedDocumentString; export const UserProfileDocument = new TypedDocumentString(` query UserProfile { viewerSession { @@ -2909,22 +2925,6 @@ export const ChangePasswordDocument = new TypedDocumentString(` } } `) as unknown as TypedDocumentString; -export const PasswordChangeDocument = new TypedDocumentString(` - query PasswordChange { - viewer { - __typename - ... on Node { - id - } - } - siteConfig { - ...PasswordCreationDoubleInput_siteConfig - } -} - fragment PasswordCreationDoubleInput_siteConfig on SiteConfig { - id - minimumPasswordComplexity -}`) as unknown as TypedDocumentString; export const RecoverPasswordDocument = new TypedDocumentString(` mutation RecoverPassword($ticket: String!, $newPassword: String!) { setPasswordByRecovery(input: {ticket: $ticket, newPassword: $newPassword}) { @@ -3326,6 +3326,27 @@ export const mockUserEmailListQuery = (resolver: GraphQLResponseResolver { + * return HttpResponse.json({ + * data: { viewer, siteConfig } + * }) + * }, + * requestOptions + * ) + */ +export const mockPasswordChangeQuery = (resolver: GraphQLResponseResolver, options?: RequestHandlerOptions) => + graphql.query( + 'PasswordChange', + resolver, + options + ) + /** * @param resolver A function that accepts [resolver arguments](https://mswjs.io/docs/api/graphql#resolver-argument) and must always return the instruction on what to do with the intercepted request. ([see more](https://mswjs.io/docs/concepts/response-resolver#resolver-instructions)) * @param options Options object to customize the behavior of the mock. ([see more](https://mswjs.io/docs/api/graphql#handler-options)) @@ -3607,27 +3628,6 @@ export const mockChangePasswordMutation = (resolver: GraphQLResponseResolver { - * return HttpResponse.json({ - * data: { viewer, siteConfig } - * }) - * }, - * requestOptions - * ) - */ -export const mockPasswordChangeQuery = (resolver: GraphQLResponseResolver, options?: RequestHandlerOptions) => - graphql.query( - 'PasswordChange', - resolver, - options - ) - /** * @param resolver A function that accepts [resolver arguments](https://mswjs.io/docs/api/graphql#resolver-argument) and must always return the instruction on what to do with the intercepted request. ([see more](https://mswjs.io/docs/concepts/response-resolver#resolver-instructions)) * @param options Options object to customize the behavior of the mock. ([see more](https://mswjs.io/docs/api/graphql#handler-options)) diff --git a/templates/pages/register/password.html b/templates/pages/register/password.html index f1a77efad..81c0939bf 100644 --- a/templates/pages/register/password.html +++ b/templates/pages/register/password.html @@ -9,6 +9,9 @@ {% extends "base.html" %} {% block content %} + + {{ include_asset('src/entrypoints/register/PasswordDoubleInput.tsx') | indent(4) | safe }} +
{{ icon.user_profile_solid() }} @@ -42,13 +45,19 @@

{{ _("mas.register.create_account.heading") }}

{% endcall %} {% endif %} - {% call(f) field.field(label=_("common.password"), name="password", form_state=form) %} - - {% endcall %} + +
- {% call(f) field.field(label=_("common.password_confirm"), name="password_confirm", form_state=form) %} - - {% endcall %} + {% if branding.tos_uri is not none %} {% call(f) field.field(label=_("mas.register.terms_of_service", tos_uri=branding.tos_uri), name="accept_terms", form_state=form, inline=true, class="my-4") %} diff --git a/translations/en.json b/translations/en.json index c8dbae97b..8c59f163b 100644 --- a/translations/en.json +++ b/translations/en.json @@ -10,7 +10,7 @@ }, "continue": "Continue", "@continue": { - "context": "form_post.html:25:28-48, pages/consent.html:57:28-48, pages/device_consent.html:124:13-33, pages/device_link.html:40:26-46, pages/login.html:68:30-50, pages/reauth.html:32:28-48, pages/recovery/start.html:38:26-46, pages/register/password.html:77:26-46, pages/register/steps/display_name.html:43:28-48, pages/register/steps/registration_token.html:41:28-48, pages/register/steps/verify_email.html:51:26-46, pages/sso.html:37:28-48" + "context": "form_post.html:25:28-48, pages/consent.html:57:28-48, pages/device_consent.html:124:13-33, pages/device_link.html:40:26-46, pages/login.html:68:30-50, pages/reauth.html:32:28-48, pages/recovery/start.html:38:26-46, pages/register/password.html:86:26-46, pages/register/steps/display_name.html:43:28-48, pages/register/steps/registration_token.html:41:28-48, pages/register/steps/verify_email.html:51:26-46, pages/sso.html:37:28-48" }, "create_account": "Create Account", "@create_account": { @@ -79,7 +79,7 @@ }, "email_address": "Email address", "@email_address": { - "context": "pages/recovery/start.html:34:33-58, pages/register/password.html:40:35-60, pages/upstream_oauth2/do_register.html:115:37-62" + "context": "pages/recovery/start.html:34:33-58, pages/register/password.html:43:35-60, pages/upstream_oauth2/do_register.html:115:37-62" }, "loading": "Loading…", "@loading": { @@ -91,15 +91,15 @@ }, "password": "Password", "@password": { - "context": "pages/login.html:56:37-57, pages/reauth.html:28:35-55, pages/register/password.html:45:33-53" + "context": "pages/login.html:56:37-57, pages/reauth.html:28:35-55, pages/register/password.html:53:35-55" }, "password_confirm": "Confirm password", "@password_confirm": { - "context": "pages/register/password.html:49:33-61" + "context": "pages/register/password.html:57:35-63" }, "username": "Username", "@username": { - "context": "pages/login.html:50:37-57, pages/register/index.html:30:35-55, pages/register/password.html:34:33-53, pages/upstream_oauth2/do_register.html:101:35-55, pages/upstream_oauth2/do_register.html:107:39-59" + "context": "pages/login.html:50:37-57, pages/register/index.html:30:35-55, pages/register/password.html:37:33-53, pages/upstream_oauth2/do_register.html:101:35-55, pages/upstream_oauth2/do_register.html:107:39-59" } }, "error": { @@ -613,7 +613,7 @@ "register": { "call_to_login": "Already have an account?", "@call_to_login": { - "context": "pages/register/index.html:63:35-66, pages/register/password.html:80:33-64", + "context": "pages/register/index.html:63:35-66, pages/register/password.html:89:33-64", "description": "Displayed on the registration page to suggest to log in instead" }, "continue_with_email": "Continue with email address", @@ -631,12 +631,12 @@ }, "heading": "Create an account", "@heading": { - "context": "pages/register/index.html:21:29-69, pages/register/password.html:18:27-67" + "context": "pages/register/index.html:21:29-69, pages/register/password.html:21:27-67" } }, "terms_of_service": "I agree to the Terms and Conditions", "@terms_of_service": { - "context": "pages/register/password.html:54:35-95, pages/upstream_oauth2/do_register.html:180:35-95" + "context": "pages/register/password.html:63:35-95, pages/upstream_oauth2/do_register.html:180:35-95" } }, "registration_token": {