Skip to content
Open
2 changes: 1 addition & 1 deletion frontend/knip.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions frontend/locales/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -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í..."
Expand Down
1 change: 1 addition & 0 deletions frontend/locales/da.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"loading": "Indlæser...",
"next": "Næste",
"password": "Adgangskode",
"password_confirm": "Bekræft adgangskode",
"previous": "Forrige",
"saved": "Gemt",
"saving": "Gemmer..."
Expand Down
1 change: 1 addition & 0 deletions frontend/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"loading": "Lade …",
"next": "Weiter",
"password": "Passwort",
"password_confirm": "Passwort wiederholen",
"previous": "Zurück",
"saved": "Gespeichert",
"saving": "Speichern..."
Expand Down
1 change: 1 addition & 0 deletions frontend/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"loading": "Loading…",
"next": "Next",
"password": "Password",
"password_confirm": "Confirm password",
"previous": "Previous",
"saved": "Saved",
"saving": "Saving…"
Expand Down
1 change: 1 addition & 0 deletions frontend/locales/et.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"loading": "Laadime…",
"next": "Edasi",
"password": "Salasõna",
"password_confirm": "Korda salasõna",
"previous": "Tagasi",
"saved": "Salvestatud",
"saving": "Salvestame…"
Expand Down
1 change: 1 addition & 0 deletions frontend/locales/fi.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"loading": "Ladataan…",
"next": "Seuraava",
"password": "Salasana",
"password_confirm": "Vahvista salasana",
"previous": "Edellinen",
"saved": "Tallennettu",
"saving": "Tallennetaan…"
Expand Down
1 change: 1 addition & 0 deletions frontend/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -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..."
Expand Down
1 change: 1 addition & 0 deletions frontend/locales/hu.json
Original file line number Diff line number Diff line change
Expand Up @@ -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…"
Expand Down
1 change: 1 addition & 0 deletions frontend/locales/nb-NO.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"loading": "Laster inn...",
"next": "Neste",
"password": "Passord",
"password_confirm": "Bekreft passord",
"previous": "Forrige",
"saved": "Lagret",
"saving": "Lagrer…"
Expand Down
1 change: 1 addition & 0 deletions frontend/locales/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"loading": "Laden...",
"next": "Volgende",
"password": "Wachtwoord",
"password_confirm": "Bevestig wachtwoord",
"previous": "Vorige",
"saved": "Opgeslagen",
"saving": "Opslaan..."
Expand Down
1 change: 1 addition & 0 deletions frontend/locales/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"loading": "Wczytywanie…",
"next": "Dalej",
"password": "Hasło",
"password_confirm": "Potwierdź hasło",
"previous": "Poprzedni",
"saved": "Zapisano",
"saving": "Zapisywanie…"
Expand Down
1 change: 1 addition & 0 deletions frontend/locales/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"loading": "A carregar...",
"next": "Seguinte",
"password": "Palavra-passe",
"password_confirm": "Confirmar palavra-passe",
"previous": "Anterior",
"saved": "Guardado",
"saving": "A guardar…"
Expand Down
1 change: 1 addition & 0 deletions frontend/locales/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"loading": "Загрузка…",
"next": "Далее",
"password": "Пароль",
"password_confirm": "Подтверждение пароля",
"previous": "Предыдущий",
"saved": "Сохранено",
"saving": "Сохранение…"
Expand Down
1 change: 1 addition & 0 deletions frontend/locales/sv.json
Original file line number Diff line number Diff line change
Expand Up @@ -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..."
Expand Down
1 change: 1 addition & 0 deletions frontend/locales/uk.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"loading": "Завантаження…",
"next": "Далі",
"password": "Пароль",
"password_confirm": "Підтвердити пароль",
"previous": "Назад",
"saved": "Збережено",
"saving": "Збереження..."
Expand Down
1 change: 1 addition & 0 deletions frontend/locales/zh-Hans.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"loading": "加载中...",
"next": "下一页",
"password": "密码",
"password_confirm": "确认密码",
"previous": "上一页",
"saved": "已保存",
"saving": "正在保存..."
Expand Down
36 changes: 27 additions & 9 deletions frontend/src/components/PasswordCreationDoubleInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof CONFIG_FRAGMENT>;
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<HTMLInputElement>(null);
const newPasswordAgainRef = useRef<HTMLInputElement>(null);
Expand All @@ -81,10 +101,8 @@ export default function PasswordCreationDoubleInput({

return (
<>
<Form.Field name="new_password">
<Form.Label>
{t("frontend.password_change.new_password_label")}
</Form.Label>
<Form.Field name={variantFields.passwordFieldName}>
<Form.Label>{variantFields.passwordLabel}</Form.Label>

<Form.PasswordControl
required
Expand Down Expand Up @@ -128,15 +146,13 @@ export default function PasswordCreationDoubleInput({
)}
</Form.Field>

<Form.Field name="new_password_again">
<Form.Field name={variantFields.passwordConfirmFieldName}>
{/*
TODO This field has validation defects,
some caused by Radix-UI upstream bugs.
https://github.com/matrix-org/matrix-authentication-service/issues/2855
*/}
<Form.Label>
{t("frontend.password_change.new_password_again_label")}
</Form.Label>
<Form.Label>{variantFields.passwordConfirmLabel}</Form.Label>

<Form.PasswordControl
required
Expand All @@ -148,7 +164,9 @@ export default function PasswordCreationDoubleInput({
{t("frontend.errors.field_required")}
</Form.ErrorMessage>

<Form.ErrorMessage match={(v, form) => v !== form.get("new_password")}>
<Form.ErrorMessage
match={(v, form) => v !== form.get(variantFields.passwordFieldName)}
>
{t("frontend.password_change.passwords_no_match")}
</Form.ErrorMessage>

Expand Down
90 changes: 90 additions & 0 deletions frontend/src/entrypoints/register/PasswordDoubleInput.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Form.Root asChild>
<div>
<PasswordCreationDoubleInput
siteConfig={siteConfig}
forceShowNewPasswordInvalid={false}
variant="register"
/>
</div>
</Form.Root>
);
}

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(
<StrictMode>
<QueryClientProvider client={queryClient}>
<ErrorBoundary>
<TooltipProvider>
<Suspense fallback={<div>{`Loading... ${containerId}…`}</div>}>
<I18nextProvider i18n={i18n}>
<PasswordDoubleInput />
</I18nextProvider>
</Suspense>
</TooltipProvider>
</ErrorBoundary>
</QueryClientProvider>
</StrictMode>,
);
} catch (err) {
console.error(
`Cannot mount component PasswordCreationDoubleInput on ${containerId}:`,
err,
);
}
}

mountComponentWithProviders(HTML_CONTAINER_ID);
12 changes: 6 additions & 6 deletions frontend/src/gql/gql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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.
*/
Expand Down
Loading
Loading