Use the new GraphQL APIs in the frontend to add emails
This commit is contained in:
@@ -49,9 +49,9 @@
|
||||
},
|
||||
"add_email_form": {
|
||||
"email_denied_error": "The entered email is not allowed by the server policy",
|
||||
"email_exists_error": "The entered email is already added to this account",
|
||||
"email_field_help": "Add an alternative email you can use to access this account.",
|
||||
"email_field_label": "Add email",
|
||||
"email_in_use_error": "The entered email is already in use",
|
||||
"email_invalid_error": "The entered email is invalid"
|
||||
},
|
||||
"browser_session_details": {
|
||||
|
||||
@@ -12,8 +12,8 @@ type Props = {
|
||||
Icon: React.ComponentType<React.SVGAttributes<SVGElement>>;
|
||||
invalid?: boolean;
|
||||
success?: boolean;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
title: React.ReactNode;
|
||||
subtitle?: React.ReactNode;
|
||||
};
|
||||
|
||||
const PageHeading: React.FC<Props> = ({
|
||||
|
||||
@@ -15,44 +15,42 @@ import { graphql } from "../../gql";
|
||||
import { graphqlRequest } from "../../graphql";
|
||||
|
||||
const ADD_EMAIL_MUTATION = graphql(/* GraphQL */ `
|
||||
mutation AddEmail($userId: ID!, $email: String!) {
|
||||
addEmail(input: { userId: $userId, email: $email }) {
|
||||
mutation AddEmail($email: String!, $language: String!) {
|
||||
startEmailAuthentication(input: { email: $email, language: $language }) {
|
||||
status
|
||||
violations
|
||||
email {
|
||||
authentication {
|
||||
id
|
||||
...UserEmail_email
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const AddEmailForm: React.FC<{
|
||||
userId: string;
|
||||
onAdd: (id: string) => Promise<void>;
|
||||
}> = ({ userId, onAdd }) => {
|
||||
const { t } = useTranslation();
|
||||
}> = ({ onAdd }) => {
|
||||
const { t, i18n } = useTranslation();
|
||||
const queryClient = useQueryClient();
|
||||
const addEmail = useMutation({
|
||||
mutationFn: ({ userId, email }: { userId: string; email: string }) =>
|
||||
mutationFn: ({ email, language }: { email: string; language: string }) =>
|
||||
graphqlRequest({
|
||||
query: ADD_EMAIL_MUTATION,
|
||||
variables: { userId, email },
|
||||
variables: { email, language },
|
||||
}),
|
||||
onSuccess: async (data) => {
|
||||
queryClient.invalidateQueries({ queryKey: ["userEmails"] });
|
||||
|
||||
// Don't clear the form if the email was invalid or already exists
|
||||
if (data.addEmail.status !== "ADDED") {
|
||||
if (data.startEmailAuthentication.status !== "STARTED") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data.addEmail.email?.id) {
|
||||
if (!data.startEmailAuthentication.authentication?.id) {
|
||||
throw new Error("Unexpected response from server");
|
||||
}
|
||||
|
||||
// Call the onAdd callback
|
||||
await onAdd(data.addEmail.email?.id);
|
||||
await onAdd(data.startEmailAuthentication.authentication?.id);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -63,20 +61,18 @@ const AddEmailForm: React.FC<{
|
||||
|
||||
const formData = new FormData(e.currentTarget);
|
||||
const email = formData.get("input") as string;
|
||||
addEmail.mutate({ userId, email });
|
||||
addEmail.mutate({ email, language: i18n.languages[0] });
|
||||
};
|
||||
|
||||
const status = addEmail.data?.addEmail.status ?? null;
|
||||
const violations = addEmail.data?.addEmail.violations ?? [];
|
||||
const status = addEmail.data?.startEmailAuthentication.status ?? null;
|
||||
const violations = addEmail.data?.startEmailAuthentication.violations ?? [];
|
||||
|
||||
return (
|
||||
<EditInPlace
|
||||
onSave={handleSubmit}
|
||||
required
|
||||
type="email"
|
||||
serverInvalid={
|
||||
status === "INVALID" || status === "EXISTS" || status === "DENIED"
|
||||
}
|
||||
serverInvalid={!!status && status !== "STARTED"}
|
||||
label={t("frontend.add_email_form.email_field_label")}
|
||||
helpLabel={t("frontend.add_email_form.email_field_help")}
|
||||
saveButtonLabel={t("action.save")}
|
||||
@@ -84,13 +80,16 @@ const AddEmailForm: React.FC<{
|
||||
savedLabel={t("common.saved")}
|
||||
cancelButtonLabel={t("action.cancel")}
|
||||
>
|
||||
<ErrorMessage match="typeMismatch" forceMatch={status === "INVALID"}>
|
||||
<ErrorMessage
|
||||
match="typeMismatch"
|
||||
forceMatch={status === "INVALID_EMAIL_ADDRESS"}
|
||||
>
|
||||
{t("frontend.add_email_form.email_invalid_error")}
|
||||
</ErrorMessage>
|
||||
|
||||
{status === "EXISTS" && (
|
||||
{status === "IN_USE" && (
|
||||
<ErrorMessage>
|
||||
{t("frontend.add_email_form.email_exists_error")}
|
||||
{t("frontend.add_email_form.email_in_use_error")}
|
||||
</ErrorMessage>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
/* Copyright 2024 New Vector Ltd.
|
||||
* Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--cpd-space-2x);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tagline {
|
||||
color: var(--cpd-color-text-secondary);
|
||||
|
||||
& > span {
|
||||
color: var(--cpd-color-text-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
height: var(--cpd-space-16x);
|
||||
width: var(--cpd-space-16x);
|
||||
color: var(--cpd-color-icon-secondary);
|
||||
background: var(--cpd-color-bg-subtle-secondary);
|
||||
padding: var(--cpd-space-2x);
|
||||
border-radius: var(--cpd-space-2x);
|
||||
margin-bottom: var(--cpd-space-2x);
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
// Copyright 2024, 2025 New Vector Ltd.
|
||||
// Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// Please see LICENSE in the repository root for full details.
|
||||
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { useLinkProps, useNavigate } from "@tanstack/react-router";
|
||||
import IconArrowLeft from "@vector-im/compound-design-tokens/assets/web/icons/arrow-left";
|
||||
import IconSend from "@vector-im/compound-design-tokens/assets/web/icons/send-solid";
|
||||
import { Alert, Button, Form, H1, Text } from "@vector-im/compound-web";
|
||||
import { useRef } from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { type FragmentType, graphql, useFragment } from "../../gql";
|
||||
import { graphqlRequest } from "../../graphql";
|
||||
import styles from "./VerifyEmail.module.css";
|
||||
|
||||
const FRAGMENT = graphql(/* GraphQL */ `
|
||||
fragment UserEmail_verifyEmail on UserEmail {
|
||||
id
|
||||
email
|
||||
}
|
||||
`);
|
||||
|
||||
const VERIFY_EMAIL_MUTATION = graphql(/* GraphQL */ `
|
||||
mutation DoVerifyEmail($id: ID!, $code: String!) {
|
||||
verifyEmail(input: { userEmailId: $id, code: $code }) {
|
||||
status
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const RESEND_VERIFICATION_EMAIL_MUTATION = graphql(/* GraphQL */ `
|
||||
mutation ResendVerificationEmail($id: ID!) {
|
||||
sendVerificationEmail(input: { userEmailId: $id }) {
|
||||
status
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const BackButton: React.FC = () => {
|
||||
const props = useLinkProps({ to: "/" });
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Button as="a" Icon={IconArrowLeft} kind="tertiary" {...props}>
|
||||
{t("action.back")}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const VerifyEmail: React.FC<{
|
||||
email: FragmentType<typeof FRAGMENT>;
|
||||
}> = ({ email }) => {
|
||||
const data = useFragment(FRAGMENT, email);
|
||||
const queryClient = useQueryClient();
|
||||
const verifyEmail = useMutation({
|
||||
mutationFn: ({ id, code }: { id: string; code: string }) =>
|
||||
graphqlRequest({ query: VERIFY_EMAIL_MUTATION, variables: { id, code } }),
|
||||
onSuccess: (data) => {
|
||||
queryClient.invalidateQueries({ queryKey: ["currentUserGreeting"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["userProfile"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["userEmails"] });
|
||||
|
||||
if (data.verifyEmail.status === "VERIFIED") {
|
||||
navigate({ to: "/" });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const resendVerificationEmail = useMutation({
|
||||
mutationFn: (id: string) =>
|
||||
graphqlRequest({
|
||||
query: RESEND_VERIFICATION_EMAIL_MUTATION,
|
||||
variables: { id },
|
||||
}),
|
||||
onSuccess: () => {
|
||||
fieldRef.current?.focus();
|
||||
},
|
||||
});
|
||||
const navigate = useNavigate();
|
||||
const fieldRef = useRef<HTMLInputElement>(null);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const onFormSubmit = (e: React.FormEvent<HTMLFormElement>): void => {
|
||||
e.preventDefault();
|
||||
const form = e.currentTarget;
|
||||
const formData = new FormData(form);
|
||||
const code = formData.get("code") as string;
|
||||
verifyEmail.mutateAsync({ id: data.id, code }).finally(() => form.reset());
|
||||
};
|
||||
|
||||
const onResendClick = (): void => {
|
||||
resendVerificationEmail.mutate(data.id);
|
||||
};
|
||||
|
||||
const emailSent =
|
||||
resendVerificationEmail.data?.sendVerificationEmail.status === "SENT";
|
||||
const invalidCode = verifyEmail.data?.verifyEmail.status === "INVALID_CODE";
|
||||
const { email: codeEmail } = data;
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className={styles.header}>
|
||||
<IconSend className={styles.icon} />
|
||||
<H1>{t("frontend.verify_email.heading")}</H1>
|
||||
<Text size="lg" className={styles.tagline}>
|
||||
<Trans
|
||||
i18nKey="frontend.verify_email.enter_code_prompt"
|
||||
values={{ email: codeEmail }}
|
||||
components={{ email: <span /> }}
|
||||
/>
|
||||
</Text>
|
||||
</header>
|
||||
|
||||
<Form.Root onSubmit={onFormSubmit}>
|
||||
{emailSent && (
|
||||
<Alert
|
||||
type="success"
|
||||
title={t("frontend.verify_email.email_sent_alert.title")}
|
||||
>
|
||||
{t("frontend.verify_email.email_sent_alert.description")}
|
||||
</Alert>
|
||||
)}
|
||||
{invalidCode && (
|
||||
<Alert
|
||||
type="critical"
|
||||
title={t("frontend.verify_email.invalid_code_alert.title")}
|
||||
>
|
||||
{t("frontend.verify_email.invalid_code_alert.description")}
|
||||
</Alert>
|
||||
)}
|
||||
<Form.Field
|
||||
name="code"
|
||||
serverInvalid={invalidCode}
|
||||
className="self-center mb-4"
|
||||
>
|
||||
<Form.Label>{t("frontend.verify_email.code_field_label")}</Form.Label>
|
||||
<Form.MFAControl ref={fieldRef} />
|
||||
|
||||
{invalidCode && (
|
||||
<Form.ErrorMessage>
|
||||
{t("frontend.verify_email.code_field_error")}
|
||||
</Form.ErrorMessage>
|
||||
)}
|
||||
|
||||
<Form.ErrorMessage match="patternMismatch">
|
||||
{t("frontend.verify_email.code_field_wrong_shape")}
|
||||
</Form.ErrorMessage>
|
||||
</Form.Field>
|
||||
|
||||
<Form.Submit type="submit" disabled={verifyEmail.isPending}>
|
||||
{t("action.continue")}
|
||||
</Form.Submit>
|
||||
<Button
|
||||
type="button"
|
||||
kind="secondary"
|
||||
disabled={resendVerificationEmail.isPending}
|
||||
onClick={onResendClick}
|
||||
>
|
||||
{t("frontend.verify_email.resend_code")}
|
||||
</Button>
|
||||
<BackButton />
|
||||
</Form.Root>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default VerifyEmail;
|
||||
@@ -1,179 +0,0 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`<VerifyEmail /> > renders an active session 1`] = `
|
||||
[
|
||||
<header
|
||||
className="_header_07ded5"
|
||||
>
|
||||
<svg
|
||||
className="_icon_07ded5"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M9.55 17.575c-.133 0-.258-.02-.375-.063a.878.878 0 0 1-.325-.212L4.55 13c-.183-.183-.27-.42-.263-.713.009-.291.105-.529.288-.712a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275L9.55 15.15l8.475-8.475c.183-.183.42-.275.713-.275.291 0 .529.092.712.275.183.183.275.42.275.713 0 .291-.092.529-.275.712l-9.2 9.2c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<h1
|
||||
className="_title_07ded5"
|
||||
>
|
||||
Verify your email
|
||||
</h1>
|
||||
<p
|
||||
className="_tagline_07ded5"
|
||||
>
|
||||
Enter the 6-digit code sent to
|
||||
|
||||
<span
|
||||
className="_email_07ded5"
|
||||
>
|
||||
ernie@sesame.st
|
||||
</span>
|
||||
</p>
|
||||
</header>,
|
||||
<form
|
||||
className="_root_xzzw3_23 _form_07ded5"
|
||||
onInvalid={[Function]}
|
||||
onReset={[Function]}
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="_field_xzzw3_32"
|
||||
>
|
||||
<label
|
||||
className="_label_xzzw3_41"
|
||||
htmlFor="radix-:r0:"
|
||||
onMouseDown={[Function]}
|
||||
>
|
||||
6-digit code
|
||||
</label>
|
||||
<input
|
||||
className="_control_xzzw3_55"
|
||||
id="radix-:r0:"
|
||||
inputMode="numeric"
|
||||
name="code"
|
||||
onChange={[Function]}
|
||||
onInvalid={[Function]}
|
||||
placeholder="xxxxxx"
|
||||
title=""
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className="_button_lls7s_17 _submitButton_07ded5 _submitButton_07ded5"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
disabled={false}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
type="submit"
|
||||
>
|
||||
Continue
|
||||
</button>
|
||||
<button
|
||||
className="_button_lls7s_17"
|
||||
data-kind="tertiary"
|
||||
data-size="lg"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
Resend email
|
||||
</button>
|
||||
</form>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`<VerifyEmail /> > renders verify screen for email 1`] = `
|
||||
[
|
||||
<header
|
||||
className="_header_07ded5"
|
||||
>
|
||||
<svg
|
||||
className="_icon_07ded5"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M9.55 17.575c-.133 0-.258-.02-.375-.063a.878.878 0 0 1-.325-.212L4.55 13c-.183-.183-.27-.42-.263-.713.009-.291.105-.529.288-.712a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275L9.55 15.15l8.475-8.475c.183-.183.42-.275.713-.275.291 0 .529.092.712.275.183.183.275.42.275.713 0 .291-.092.529-.275.712l-9.2 9.2c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<h1
|
||||
className="_title_07ded5"
|
||||
>
|
||||
Verify your email
|
||||
</h1>
|
||||
<p
|
||||
className="_tagline_07ded5"
|
||||
>
|
||||
Enter the 6-digit code sent to
|
||||
|
||||
<span
|
||||
className="_email_07ded5"
|
||||
>
|
||||
ernie@sesame.st
|
||||
</span>
|
||||
</p>
|
||||
</header>,
|
||||
<form
|
||||
className="_root_xzzw3_23 _form_07ded5"
|
||||
onInvalid={[Function]}
|
||||
onReset={[Function]}
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="_field_xzzw3_32"
|
||||
>
|
||||
<label
|
||||
className="_label_xzzw3_41"
|
||||
htmlFor="radix-:r0:"
|
||||
onMouseDown={[Function]}
|
||||
>
|
||||
6-digit code
|
||||
</label>
|
||||
<input
|
||||
className="_control_xzzw3_55"
|
||||
id="radix-:r0:"
|
||||
inputMode="numeric"
|
||||
name="code"
|
||||
onChange={[Function]}
|
||||
onInvalid={[Function]}
|
||||
placeholder="xxxxxx"
|
||||
title=""
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className="_button_lls7s_17 _submitButton_07ded5 _submitButton_07ded5"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
disabled={false}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
type="submit"
|
||||
>
|
||||
Continue
|
||||
</button>
|
||||
<button
|
||||
className="_button_lls7s_17"
|
||||
data-kind="tertiary"
|
||||
data-size="lg"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
Resend email
|
||||
</button>
|
||||
</form>,
|
||||
]
|
||||
`;
|
||||
@@ -1,7 +0,0 @@
|
||||
// Copyright 2024 New Vector Ltd.
|
||||
// Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// Please see LICENSE in the repository root for full details.
|
||||
|
||||
export { default } from "./VerifyEmail";
|
||||
@@ -35,14 +35,11 @@ const documents = {
|
||||
"\n fragment UserGreeting_user on User {\n id\n matrix {\n mxid\n displayName\n }\n }\n": types.UserGreeting_UserFragmentDoc,
|
||||
"\n fragment UserGreeting_siteConfig on SiteConfig {\n displayNameChangeAllowed\n }\n": types.UserGreeting_SiteConfigFragmentDoc,
|
||||
"\n mutation SetDisplayName($userId: ID!, $displayName: String) {\n setDisplayName(input: { userId: $userId, displayName: $displayName }) {\n status\n }\n }\n": types.SetDisplayNameDocument,
|
||||
"\n mutation AddEmail($userId: ID!, $email: String!) {\n addEmail(input: { userId: $userId, email: $email }) {\n status\n violations\n email {\n id\n ...UserEmail_email\n }\n }\n }\n": types.AddEmailDocument,
|
||||
"\n mutation AddEmail($email: String!, $language: String!) {\n startEmailAuthentication(input: { email: $email, language: $language }) {\n status\n violations\n authentication {\n id\n }\n }\n }\n": types.AddEmailDocument,
|
||||
"\n query UserEmailList(\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n viewer {\n __typename\n ... on User {\n emails(first: $first, after: $after, last: $last, before: $before) {\n edges {\n cursor\n node {\n ...UserEmail_email\n }\n }\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n }\n": types.UserEmailListDocument,
|
||||
"\n fragment UserEmailList_siteConfig on SiteConfig {\n emailChangeAllowed\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 fragment UserEmail_verifyEmail on UserEmail {\n id\n email\n }\n": types.UserEmail_VerifyEmailFragmentDoc,
|
||||
"\n mutation DoVerifyEmail($id: ID!, $code: String!) {\n verifyEmail(input: { userEmailId: $id, code: $code }) {\n status\n }\n }\n": types.DoVerifyEmailDocument,
|
||||
"\n mutation ResendVerificationEmail($id: ID!) {\n sendVerificationEmail(input: { userEmailId: $id }) {\n status\n }\n }\n": types.ResendVerificationEmailDocument,
|
||||
"\n query UserProfile {\n viewer {\n __typename\n ... on User {\n id\n\n emails(first: 0) {\n totalCount\n }\n }\n }\n\n siteConfig {\n emailChangeAllowed\n passwordLoginEnabled\n ...UserEmailList_siteConfig\n ...UserEmail_siteConfig\n ...PasswordChange_siteConfig\n }\n }\n": types.UserProfileDocument,
|
||||
"\n query UserProfile {\n viewer {\n __typename\n ... on User {\n emails(first: 0) {\n totalCount\n }\n }\n }\n\n siteConfig {\n emailChangeAllowed\n passwordLoginEnabled\n ...UserEmailList_siteConfig\n ...UserEmail_siteConfig\n ...PasswordChange_siteConfig\n }\n }\n": types.UserProfileDocument,
|
||||
"\n query SessionDetail($id: ID!) {\n viewerSession {\n ... on Node {\n id\n }\n }\n\n node(id: $id) {\n __typename\n id\n ...CompatSession_detail\n ...OAuth2Session_detail\n ...BrowserSession_detail\n }\n }\n": types.SessionDetailDocument,
|
||||
"\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,
|
||||
"\n query SessionsOverview {\n viewer {\n __typename\n\n ... on User {\n id\n ...BrowserSessionsOverview_user\n }\n }\n }\n": types.SessionsOverviewDocument,
|
||||
@@ -51,7 +48,9 @@ const documents = {
|
||||
"\n query OAuth2Client($id: ID!) {\n oauth2Client(id: $id) {\n ...OAuth2Client_detail\n }\n }\n": types.OAuth2ClientDocument,
|
||||
"\n query CurrentViewer {\n viewer {\n __typename\n ... on Node {\n id\n }\n }\n }\n": types.CurrentViewerDocument,
|
||||
"\n query DeviceRedirect($deviceId: String!, $userId: ID!) {\n session(deviceId: $deviceId, userId: $userId) {\n __typename\n ... on Node {\n id\n }\n }\n }\n": types.DeviceRedirectDocument,
|
||||
"\n query VerifyEmail($id: ID!) {\n userEmail(id: $id) {\n ...UserEmail_verifyEmail\n }\n }\n": types.VerifyEmailDocument,
|
||||
"\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 query VerifyEmail($id: ID!) {\n userEmailAuthentication(id: $id) {\n id\n email\n completedAt\n }\n }\n": types.VerifyEmailDocument,
|
||||
"\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,
|
||||
@@ -145,7 +144,7 @@ export function graphql(source: "\n mutation SetDisplayName($userId: ID!, $disp
|
||||
/**
|
||||
* 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 AddEmail($userId: ID!, $email: String!) {\n addEmail(input: { userId: $userId, email: $email }) {\n status\n violations\n email {\n id\n ...UserEmail_email\n }\n }\n }\n"): typeof import('./graphql').AddEmailDocument;
|
||||
export function graphql(source: "\n mutation AddEmail($email: String!, $language: String!) {\n startEmailAuthentication(input: { email: $email, language: $language }) {\n status\n violations\n authentication {\n id\n }\n }\n }\n"): typeof import('./graphql').AddEmailDocument;
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -161,19 +160,7 @@ export function graphql(source: "\n fragment BrowserSessionsOverview_user on Us
|
||||
/**
|
||||
* 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 UserEmail_verifyEmail on UserEmail {\n id\n email\n }\n"): typeof import('./graphql').UserEmail_VerifyEmailFragmentDoc;
|
||||
/**
|
||||
* 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 DoVerifyEmail($id: ID!, $code: String!) {\n verifyEmail(input: { userEmailId: $id, code: $code }) {\n status\n }\n }\n"): typeof import('./graphql').DoVerifyEmailDocument;
|
||||
/**
|
||||
* 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 ResendVerificationEmail($id: ID!) {\n sendVerificationEmail(input: { userEmailId: $id }) {\n status\n }\n }\n"): typeof import('./graphql').ResendVerificationEmailDocument;
|
||||
/**
|
||||
* 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 UserProfile {\n viewer {\n __typename\n ... on User {\n id\n\n emails(first: 0) {\n totalCount\n }\n }\n }\n\n siteConfig {\n emailChangeAllowed\n passwordLoginEnabled\n ...UserEmailList_siteConfig\n ...UserEmail_siteConfig\n ...PasswordChange_siteConfig\n }\n }\n"): typeof import('./graphql').UserProfileDocument;
|
||||
export function graphql(source: "\n query UserProfile {\n viewer {\n __typename\n ... on User {\n emails(first: 0) {\n totalCount\n }\n }\n }\n\n siteConfig {\n emailChangeAllowed\n passwordLoginEnabled\n ...UserEmailList_siteConfig\n ...UserEmail_siteConfig\n ...PasswordChange_siteConfig\n }\n }\n"): typeof import('./graphql').UserProfileDocument;
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -209,7 +196,15 @@ export function graphql(source: "\n query DeviceRedirect($deviceId: String!, $u
|
||||
/**
|
||||
* 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 VerifyEmail($id: ID!) {\n userEmail(id: $id) {\n ...UserEmail_verifyEmail\n }\n }\n"): typeof import('./graphql').VerifyEmailDocument;
|
||||
export function graphql(source: "\n mutation DoVerifyEmail($id: ID!, $code: String!) {\n completeEmailAuthentication(input: { id: $id, code: $code }) {\n status\n }\n }\n"): typeof import('./graphql').DoVerifyEmailDocument;
|
||||
/**
|
||||
* 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 ResendEmailAuthenticationCode($id: ID!, $language: String!) {\n resendEmailAuthenticationCode(input: { id: $id, language: $language }) {\n status\n }\n }\n"): typeof import('./graphql').ResendEmailAuthenticationCodeDocument;
|
||||
/**
|
||||
* 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 VerifyEmail($id: ID!) {\n userEmailAuthentication(id: $id) {\n id\n email\n completedAt\n }\n }\n"): typeof import('./graphql').VerifyEmailDocument;
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
|
||||
@@ -326,6 +326,30 @@ export type CompatSsoLoginEdge = {
|
||||
node: CompatSsoLogin;
|
||||
};
|
||||
|
||||
/** The input for the `completeEmailAuthentication` mutation */
|
||||
export type CompleteEmailAuthenticationInput = {
|
||||
/** The authentication code to use */
|
||||
code: Scalars['String']['input'];
|
||||
/** The ID of the authentication session to complete */
|
||||
id: Scalars['ID']['input'];
|
||||
};
|
||||
|
||||
/** The payload of the `completeEmailAuthentication` mutation */
|
||||
export type CompleteEmailAuthenticationPayload = {
|
||||
__typename?: 'CompleteEmailAuthenticationPayload';
|
||||
/** Status of the operation */
|
||||
status: CompleteEmailAuthenticationStatus;
|
||||
};
|
||||
|
||||
/** The status of the `completeEmailAuthentication` mutation */
|
||||
export type CompleteEmailAuthenticationStatus =
|
||||
/** The authentication code has expired */
|
||||
| 'CODE_EXPIRED'
|
||||
/** The authentication was completed */
|
||||
| 'COMPLETED'
|
||||
/** The authentication code is invalid */
|
||||
| 'INVALID_CODE';
|
||||
|
||||
/** The input of the `createOauth2Session` mutation. */
|
||||
export type CreateOAuth2SessionInput = {
|
||||
/** Whether the session should issue a never-expiring access token */
|
||||
@@ -474,12 +498,17 @@ export type MatrixUser = {
|
||||
/** The mutations root of the GraphQL interface. */
|
||||
export type Mutation = {
|
||||
__typename?: 'Mutation';
|
||||
/** Add an email address to the specified user */
|
||||
/**
|
||||
* Add an email address to the specified user
|
||||
* @deprecated Use `startEmailAuthentication` instead.
|
||||
*/
|
||||
addEmail: AddEmailPayload;
|
||||
/** Add a user. This is only available to administrators. */
|
||||
addUser: AddUserPayload;
|
||||
/** Temporarily allow user to reset their cross-signing keys. */
|
||||
allowUserCrossSigningReset: AllowUserCrossSigningResetPayload;
|
||||
/** Complete the email authentication flow */
|
||||
completeEmailAuthentication: CompleteEmailAuthenticationPayload;
|
||||
/**
|
||||
* Create a new arbitrary OAuth 2.0 Session.
|
||||
*
|
||||
@@ -493,6 +522,8 @@ export type Mutation = {
|
||||
lockUser: LockUserPayload;
|
||||
/** Remove an email address */
|
||||
removeEmail: RemoveEmailPayload;
|
||||
/** Resend the email authentication code */
|
||||
resendEmailAuthenticationCode: ResendEmailAuthenticationCodePayload;
|
||||
/**
|
||||
* Resend a user recovery email
|
||||
*
|
||||
@@ -501,8 +532,6 @@ export type Mutation = {
|
||||
* calls this mutation.
|
||||
*/
|
||||
resendRecoveryEmail: ResendRecoveryEmailPayload;
|
||||
/** Send a verification code for an email address */
|
||||
sendVerificationEmail: SendVerificationEmailPayload;
|
||||
/**
|
||||
* Set whether a user can request admin. This is only available to
|
||||
* administrators.
|
||||
@@ -526,10 +555,10 @@ export type Mutation = {
|
||||
* @deprecated This doesn't do anything anymore, but is kept to avoid breaking existing queries
|
||||
*/
|
||||
setPrimaryEmail: SetPrimaryEmailPayload;
|
||||
/** Start a new email authentication flow */
|
||||
startEmailAuthentication: StartEmailAuthenticationPayload;
|
||||
/** Unlock a user. This is only available to administrators. */
|
||||
unlockUser: UnlockUserPayload;
|
||||
/** Submit a verification code for an email address */
|
||||
verifyEmail: VerifyEmailPayload;
|
||||
};
|
||||
|
||||
|
||||
@@ -551,6 +580,12 @@ export type MutationAllowUserCrossSigningResetArgs = {
|
||||
};
|
||||
|
||||
|
||||
/** The mutations root of the GraphQL interface. */
|
||||
export type MutationCompleteEmailAuthenticationArgs = {
|
||||
input: CompleteEmailAuthenticationInput;
|
||||
};
|
||||
|
||||
|
||||
/** The mutations root of the GraphQL interface. */
|
||||
export type MutationCreateOauth2SessionArgs = {
|
||||
input: CreateOAuth2SessionInput;
|
||||
@@ -588,14 +623,14 @@ export type MutationRemoveEmailArgs = {
|
||||
|
||||
|
||||
/** The mutations root of the GraphQL interface. */
|
||||
export type MutationResendRecoveryEmailArgs = {
|
||||
input: ResendRecoveryEmailInput;
|
||||
export type MutationResendEmailAuthenticationCodeArgs = {
|
||||
input: ResendEmailAuthenticationCodeInput;
|
||||
};
|
||||
|
||||
|
||||
/** The mutations root of the GraphQL interface. */
|
||||
export type MutationSendVerificationEmailArgs = {
|
||||
input: SendVerificationEmailInput;
|
||||
export type MutationResendRecoveryEmailArgs = {
|
||||
input: ResendRecoveryEmailInput;
|
||||
};
|
||||
|
||||
|
||||
@@ -630,14 +665,14 @@ export type MutationSetPrimaryEmailArgs = {
|
||||
|
||||
|
||||
/** The mutations root of the GraphQL interface. */
|
||||
export type MutationUnlockUserArgs = {
|
||||
input: UnlockUserInput;
|
||||
export type MutationStartEmailAuthenticationArgs = {
|
||||
input: StartEmailAuthenticationInput;
|
||||
};
|
||||
|
||||
|
||||
/** The mutations root of the GraphQL interface. */
|
||||
export type MutationVerifyEmailArgs = {
|
||||
input: VerifyEmailInput;
|
||||
export type MutationUnlockUserArgs = {
|
||||
input: UnlockUserInput;
|
||||
};
|
||||
|
||||
/** An object with an ID. */
|
||||
@@ -779,6 +814,8 @@ export type Query = {
|
||||
userByUsername?: Maybe<User>;
|
||||
/** Fetch a user email by its ID. */
|
||||
userEmail?: Maybe<UserEmail>;
|
||||
/** Fetch a user email authentication session */
|
||||
userEmailAuthentication?: Maybe<UserEmailAuthentication>;
|
||||
/** Fetch a user recovery ticket. */
|
||||
userRecoveryTicket?: Maybe<UserRecoveryTicket>;
|
||||
/**
|
||||
@@ -870,6 +907,12 @@ export type QueryUserEmailArgs = {
|
||||
};
|
||||
|
||||
|
||||
/** The query root of the GraphQL interface. */
|
||||
export type QueryUserEmailAuthenticationArgs = {
|
||||
id: Scalars['ID']['input'];
|
||||
};
|
||||
|
||||
|
||||
/** The query root of the GraphQL interface. */
|
||||
export type QueryUserRecoveryTicketArgs = {
|
||||
ticket: Scalars['String']['input'];
|
||||
@@ -910,6 +953,28 @@ export type RemoveEmailStatus =
|
||||
/** The email address was removed */
|
||||
| 'REMOVED';
|
||||
|
||||
/** The input for the `resendEmailAuthenticationCode` mutation */
|
||||
export type ResendEmailAuthenticationCodeInput = {
|
||||
/** The ID of the authentication session to resend the code for */
|
||||
id: Scalars['ID']['input'];
|
||||
/** The language to use for the email */
|
||||
language?: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
/** The payload of the `resendEmailAuthenticationCode` mutation */
|
||||
export type ResendEmailAuthenticationCodePayload = {
|
||||
__typename?: 'ResendEmailAuthenticationCodePayload';
|
||||
/** Status of the operation */
|
||||
status: ResendEmailAuthenticationCodeStatus;
|
||||
};
|
||||
|
||||
/** The status of the `resendEmailAuthenticationCode` mutation */
|
||||
export type ResendEmailAuthenticationCodeStatus =
|
||||
/** The email authentication session is already completed */
|
||||
| 'COMPLETED'
|
||||
/** The email was resent */
|
||||
| 'RESENT';
|
||||
|
||||
/** The input for the `resendRecoveryEmail` mutation. */
|
||||
export type ResendRecoveryEmailInput = {
|
||||
/** The recovery ticket to use. */
|
||||
@@ -934,28 +999,6 @@ export type ResendRecoveryEmailStatus =
|
||||
/** The recovery email was sent. */
|
||||
| 'SENT';
|
||||
|
||||
/** The input for the `sendVerificationEmail` mutation */
|
||||
export type SendVerificationEmailInput = {
|
||||
/** The ID of the email address to verify */
|
||||
userEmailId: Scalars['ID']['input'];
|
||||
};
|
||||
|
||||
/** The payload of the `sendVerificationEmail` mutation */
|
||||
export type SendVerificationEmailPayload = {
|
||||
__typename?: 'SendVerificationEmailPayload';
|
||||
/** The email address to which the verification email was sent */
|
||||
email: UserEmail;
|
||||
/** Status of the operation */
|
||||
status: SendVerificationEmailStatus;
|
||||
/** The user to whom the email address belongs */
|
||||
user: User;
|
||||
};
|
||||
|
||||
/** The status of the `sendVerificationEmail` mutation */
|
||||
export type SendVerificationEmailStatus =
|
||||
/** The email address is already verified */
|
||||
| 'ALREADY_VERIFIED';
|
||||
|
||||
/** A client session, either compat or OAuth 2.0 */
|
||||
export type Session = CompatSession | Oauth2Session;
|
||||
|
||||
@@ -1135,6 +1178,36 @@ export type SiteConfig = Node & {
|
||||
tosUri?: Maybe<Scalars['Url']['output']>;
|
||||
};
|
||||
|
||||
/** The input for the `startEmailAuthentication` mutation */
|
||||
export type StartEmailAuthenticationInput = {
|
||||
/** The email address to add to the account */
|
||||
email: Scalars['String']['input'];
|
||||
/** The language to use for the email */
|
||||
language?: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
/** The payload of the `startEmailAuthentication` mutation */
|
||||
export type StartEmailAuthenticationPayload = {
|
||||
__typename?: 'StartEmailAuthenticationPayload';
|
||||
/** The email authentication session that was started */
|
||||
authentication?: Maybe<UserEmailAuthentication>;
|
||||
/** Status of the operation */
|
||||
status: StartEmailAuthenticationStatus;
|
||||
/** The list of policy violations if the email address was denied */
|
||||
violations?: Maybe<Array<Scalars['String']['output']>>;
|
||||
};
|
||||
|
||||
/** The status of the `startEmailAuthentication` mutation */
|
||||
export type StartEmailAuthenticationStatus =
|
||||
/** The email address isn't allowed by the policy */
|
||||
| 'DENIED'
|
||||
/** The email address is invalid */
|
||||
| 'INVALID_EMAIL_ADDRESS'
|
||||
/** The email address is already in use */
|
||||
| 'IN_USE'
|
||||
/** The email address was started */
|
||||
| 'STARTED';
|
||||
|
||||
/** The input for the `unlockUser` mutation. */
|
||||
export type UnlockUserInput = {
|
||||
/** The ID of the user to unlock */
|
||||
@@ -1404,6 +1477,19 @@ export type UserEmail = CreationEvent & Node & {
|
||||
id: Scalars['ID']['output'];
|
||||
};
|
||||
|
||||
/** A email authentication session */
|
||||
export type UserEmailAuthentication = CreationEvent & Node & {
|
||||
__typename?: 'UserEmailAuthentication';
|
||||
/** When the object was last updated. */
|
||||
completedAt?: Maybe<Scalars['DateTime']['output']>;
|
||||
/** When the object was created. */
|
||||
createdAt: Scalars['DateTime']['output'];
|
||||
/** The email address associated with this session */
|
||||
email: Scalars['String']['output'];
|
||||
/** ID of the object. */
|
||||
id: Scalars['ID']['output'];
|
||||
};
|
||||
|
||||
export type UserEmailConnection = {
|
||||
__typename?: 'UserEmailConnection';
|
||||
/** A list of edges. */
|
||||
@@ -1463,30 +1549,6 @@ export type UserState =
|
||||
/** The user is locked. */
|
||||
| 'LOCKED';
|
||||
|
||||
/** The input for the `verifyEmail` mutation */
|
||||
export type VerifyEmailInput = {
|
||||
/** The verification code */
|
||||
code: Scalars['String']['input'];
|
||||
/** The ID of the email address to verify */
|
||||
userEmailId: Scalars['ID']['input'];
|
||||
};
|
||||
|
||||
/** The payload of the `verifyEmail` mutation */
|
||||
export type VerifyEmailPayload = {
|
||||
__typename?: 'VerifyEmailPayload';
|
||||
/** The email address that was verified */
|
||||
email?: Maybe<UserEmail>;
|
||||
/** Status of the operation */
|
||||
status: VerifyEmailStatus;
|
||||
/** The user to whom the email address belongs */
|
||||
user?: Maybe<User>;
|
||||
};
|
||||
|
||||
/** The status of the `verifyEmail` mutation */
|
||||
export type VerifyEmailStatus =
|
||||
/** The email address was already verified before */
|
||||
| 'ALREADY_VERIFIED';
|
||||
|
||||
/** Represents the current viewer */
|
||||
export type Viewer = Anonymous | User;
|
||||
|
||||
@@ -1569,15 +1631,12 @@ export type SetDisplayNameMutationVariables = Exact<{
|
||||
export type SetDisplayNameMutation = { __typename?: 'Mutation', setDisplayName: { __typename?: 'SetDisplayNamePayload', status: SetDisplayNameStatus } };
|
||||
|
||||
export type AddEmailMutationVariables = Exact<{
|
||||
userId: Scalars['ID']['input'];
|
||||
email: Scalars['String']['input'];
|
||||
language: Scalars['String']['input'];
|
||||
}>;
|
||||
|
||||
|
||||
export type AddEmailMutation = { __typename?: 'Mutation', addEmail: { __typename?: 'AddEmailPayload', status: AddEmailStatus, violations?: Array<string> | null, email?: (
|
||||
{ __typename?: 'UserEmail', id: string }
|
||||
& { ' $fragmentRefs'?: { 'UserEmail_EmailFragment': UserEmail_EmailFragment } }
|
||||
) | null } };
|
||||
export type AddEmailMutation = { __typename?: 'Mutation', startEmailAuthentication: { __typename?: 'StartEmailAuthenticationPayload', status: StartEmailAuthenticationStatus, violations?: Array<string> | null, authentication?: { __typename?: 'UserEmailAuthentication', id: string } | null } };
|
||||
|
||||
export type UserEmailListQueryVariables = Exact<{
|
||||
first?: InputMaybe<Scalars['Int']['input']>;
|
||||
@@ -1596,27 +1655,10 @@ 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 UserEmail_VerifyEmailFragment = { __typename?: 'UserEmail', id: string, email: string } & { ' $fragmentName'?: 'UserEmail_VerifyEmailFragment' };
|
||||
|
||||
export type DoVerifyEmailMutationVariables = Exact<{
|
||||
id: Scalars['ID']['input'];
|
||||
code: Scalars['String']['input'];
|
||||
}>;
|
||||
|
||||
|
||||
export type DoVerifyEmailMutation = { __typename?: 'Mutation', verifyEmail: { __typename?: 'VerifyEmailPayload', status: VerifyEmailStatus } };
|
||||
|
||||
export type ResendVerificationEmailMutationVariables = Exact<{
|
||||
id: Scalars['ID']['input'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ResendVerificationEmailMutation = { __typename?: 'Mutation', sendVerificationEmail: { __typename?: 'SendVerificationEmailPayload', status: SendVerificationEmailStatus } };
|
||||
|
||||
export type UserProfileQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type UserProfileQuery = { __typename?: 'Query', viewer: { __typename: 'Anonymous' } | { __typename: 'User', id: string, emails: { __typename?: 'UserEmailConnection', totalCount: number } }, siteConfig: (
|
||||
export type UserProfileQuery = { __typename?: 'Query', viewer: { __typename: 'Anonymous' } | { __typename: 'User', emails: { __typename?: 'UserEmailConnection', totalCount: number } }, siteConfig: (
|
||||
{ __typename?: 'SiteConfig', emailChangeAllowed: boolean, passwordLoginEnabled: boolean }
|
||||
& { ' $fragmentRefs'?: { 'UserEmailList_SiteConfigFragment': UserEmailList_SiteConfigFragment;'UserEmail_SiteConfigFragment': UserEmail_SiteConfigFragment;'PasswordChange_SiteConfigFragment': PasswordChange_SiteConfigFragment } }
|
||||
) };
|
||||
@@ -1635,7 +1677,7 @@ export type SessionDetailQuery = { __typename?: 'Query', viewerSession: { __type
|
||||
) | { __typename: 'CompatSsoLogin', id: string } | { __typename: 'Oauth2Client', id: string } | (
|
||||
{ __typename: 'Oauth2Session', id: string }
|
||||
& { ' $fragmentRefs'?: { 'OAuth2Session_DetailFragment': OAuth2Session_DetailFragment } }
|
||||
) | { __typename: 'SiteConfig', id: string } | { __typename: 'UpstreamOAuth2Link', id: string } | { __typename: 'UpstreamOAuth2Provider', id: string } | { __typename: 'User', id: string } | { __typename: 'UserEmail', id: string } | { __typename: 'UserRecoveryTicket', id: string } | null };
|
||||
) | { __typename: 'SiteConfig', id: string } | { __typename: 'UpstreamOAuth2Link', id: string } | { __typename: 'UpstreamOAuth2Provider', id: string } | { __typename: 'User', id: string } | { __typename: 'UserEmail', id: string } | { __typename: 'UserEmailAuthentication', id: string } | { __typename: 'UserRecoveryTicket', id: string } | null };
|
||||
|
||||
export type BrowserSessionListQueryVariables = Exact<{
|
||||
first?: InputMaybe<Scalars['Int']['input']>;
|
||||
@@ -1710,15 +1752,28 @@ export type DeviceRedirectQueryVariables = Exact<{
|
||||
|
||||
export type DeviceRedirectQuery = { __typename?: 'Query', session?: { __typename: 'CompatSession', id: string } | { __typename: 'Oauth2Session', id: string } | null };
|
||||
|
||||
export type DoVerifyEmailMutationVariables = Exact<{
|
||||
id: Scalars['ID']['input'];
|
||||
code: Scalars['String']['input'];
|
||||
}>;
|
||||
|
||||
|
||||
export type DoVerifyEmailMutation = { __typename?: 'Mutation', completeEmailAuthentication: { __typename?: 'CompleteEmailAuthenticationPayload', status: CompleteEmailAuthenticationStatus } };
|
||||
|
||||
export type ResendEmailAuthenticationCodeMutationVariables = Exact<{
|
||||
id: Scalars['ID']['input'];
|
||||
language: Scalars['String']['input'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ResendEmailAuthenticationCodeMutation = { __typename?: 'Mutation', resendEmailAuthenticationCode: { __typename?: 'ResendEmailAuthenticationCodePayload', status: ResendEmailAuthenticationCodeStatus } };
|
||||
|
||||
export type VerifyEmailQueryVariables = Exact<{
|
||||
id: Scalars['ID']['input'];
|
||||
}>;
|
||||
|
||||
|
||||
export type VerifyEmailQuery = { __typename?: 'Query', userEmail?: (
|
||||
{ __typename?: 'UserEmail' }
|
||||
& { ' $fragmentRefs'?: { 'UserEmail_VerifyEmailFragment': UserEmail_VerifyEmailFragment } }
|
||||
) | null };
|
||||
export type VerifyEmailQuery = { __typename?: 'Query', userEmailAuthentication?: { __typename?: 'UserEmailAuthentication', id: string, email: string, completedAt?: string | null } | null };
|
||||
|
||||
export type ChangePasswordMutationVariables = Exact<{
|
||||
userId: Scalars['ID']['input'];
|
||||
@@ -1977,12 +2032,6 @@ export const BrowserSessionsOverview_UserFragmentDoc = new TypedDocumentString(`
|
||||
}
|
||||
}
|
||||
`, {"fragmentName":"BrowserSessionsOverview_user"}) as unknown as TypedDocumentString<BrowserSessionsOverview_UserFragment, unknown>;
|
||||
export const UserEmail_VerifyEmailFragmentDoc = new TypedDocumentString(`
|
||||
fragment UserEmail_verifyEmail on UserEmail {
|
||||
id
|
||||
email
|
||||
}
|
||||
`, {"fragmentName":"UserEmail_verifyEmail"}) as unknown as TypedDocumentString<UserEmail_VerifyEmailFragment, unknown>;
|
||||
export const RecoverPassword_UserRecoveryTicketFragmentDoc = new TypedDocumentString(`
|
||||
fragment RecoverPassword_userRecoveryTicket on UserRecoveryTicket {
|
||||
username
|
||||
@@ -2082,20 +2131,16 @@ export const SetDisplayNameDocument = new TypedDocumentString(`
|
||||
}
|
||||
`) as unknown as TypedDocumentString<SetDisplayNameMutation, SetDisplayNameMutationVariables>;
|
||||
export const AddEmailDocument = new TypedDocumentString(`
|
||||
mutation AddEmail($userId: ID!, $email: String!) {
|
||||
addEmail(input: {userId: $userId, email: $email}) {
|
||||
mutation AddEmail($email: String!, $language: String!) {
|
||||
startEmailAuthentication(input: {email: $email, language: $language}) {
|
||||
status
|
||||
violations
|
||||
email {
|
||||
authentication {
|
||||
id
|
||||
...UserEmail_email
|
||||
}
|
||||
}
|
||||
}
|
||||
fragment UserEmail_email on UserEmail {
|
||||
id
|
||||
email
|
||||
}`) as unknown as TypedDocumentString<AddEmailMutation, AddEmailMutationVariables>;
|
||||
`) as unknown as TypedDocumentString<AddEmailMutation, AddEmailMutationVariables>;
|
||||
export const UserEmailListDocument = new TypedDocumentString(`
|
||||
query UserEmailList($first: Int, $after: String, $last: Int, $before: String) {
|
||||
viewer {
|
||||
@@ -2123,26 +2168,11 @@ export const UserEmailListDocument = new TypedDocumentString(`
|
||||
id
|
||||
email
|
||||
}`) as unknown as TypedDocumentString<UserEmailListQuery, UserEmailListQueryVariables>;
|
||||
export const DoVerifyEmailDocument = new TypedDocumentString(`
|
||||
mutation DoVerifyEmail($id: ID!, $code: String!) {
|
||||
verifyEmail(input: {userEmailId: $id, code: $code}) {
|
||||
status
|
||||
}
|
||||
}
|
||||
`) as unknown as TypedDocumentString<DoVerifyEmailMutation, DoVerifyEmailMutationVariables>;
|
||||
export const ResendVerificationEmailDocument = new TypedDocumentString(`
|
||||
mutation ResendVerificationEmail($id: ID!) {
|
||||
sendVerificationEmail(input: {userEmailId: $id}) {
|
||||
status
|
||||
}
|
||||
}
|
||||
`) as unknown as TypedDocumentString<ResendVerificationEmailMutation, ResendVerificationEmailMutationVariables>;
|
||||
export const UserProfileDocument = new TypedDocumentString(`
|
||||
query UserProfile {
|
||||
viewer {
|
||||
__typename
|
||||
... on User {
|
||||
id
|
||||
emails(first: 0) {
|
||||
totalCount
|
||||
}
|
||||
@@ -2434,16 +2464,29 @@ export const DeviceRedirectDocument = new TypedDocumentString(`
|
||||
}
|
||||
}
|
||||
`) as unknown as TypedDocumentString<DeviceRedirectQuery, DeviceRedirectQueryVariables>;
|
||||
export const VerifyEmailDocument = new TypedDocumentString(`
|
||||
query VerifyEmail($id: ID!) {
|
||||
userEmail(id: $id) {
|
||||
...UserEmail_verifyEmail
|
||||
export const DoVerifyEmailDocument = new TypedDocumentString(`
|
||||
mutation DoVerifyEmail($id: ID!, $code: String!) {
|
||||
completeEmailAuthentication(input: {id: $id, code: $code}) {
|
||||
status
|
||||
}
|
||||
}
|
||||
fragment UserEmail_verifyEmail on UserEmail {
|
||||
id
|
||||
email
|
||||
}`) as unknown as TypedDocumentString<VerifyEmailQuery, VerifyEmailQueryVariables>;
|
||||
`) as unknown as TypedDocumentString<DoVerifyEmailMutation, DoVerifyEmailMutationVariables>;
|
||||
export const ResendEmailAuthenticationCodeDocument = new TypedDocumentString(`
|
||||
mutation ResendEmailAuthenticationCode($id: ID!, $language: String!) {
|
||||
resendEmailAuthenticationCode(input: {id: $id, language: $language}) {
|
||||
status
|
||||
}
|
||||
}
|
||||
`) as unknown as TypedDocumentString<ResendEmailAuthenticationCodeMutation, ResendEmailAuthenticationCodeMutationVariables>;
|
||||
export const VerifyEmailDocument = new TypedDocumentString(`
|
||||
query VerifyEmail($id: ID!) {
|
||||
userEmailAuthentication(id: $id) {
|
||||
id
|
||||
email
|
||||
completedAt
|
||||
}
|
||||
}
|
||||
`) as unknown as TypedDocumentString<VerifyEmailQuery, VerifyEmailQueryVariables>;
|
||||
export const ChangePasswordDocument = new TypedDocumentString(`
|
||||
mutation ChangePassword($userId: ID!, $oldPassword: String!, $newPassword: String!) {
|
||||
setPassword(
|
||||
@@ -2653,9 +2696,9 @@ export const mockSetDisplayNameMutation = (resolver: GraphQLResponseResolver<Set
|
||||
* @example
|
||||
* mockAddEmailMutation(
|
||||
* ({ query, variables }) => {
|
||||
* const { userId, email } = variables;
|
||||
* const { email, language } = variables;
|
||||
* return HttpResponse.json({
|
||||
* data: { addEmail }
|
||||
* data: { startEmailAuthentication }
|
||||
* })
|
||||
* },
|
||||
* requestOptions
|
||||
@@ -2690,50 +2733,6 @@ export const mockUserEmailListQuery = (resolver: GraphQLResponseResolver<UserEma
|
||||
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))
|
||||
* @see https://mswjs.io/docs/basics/response-resolver
|
||||
* @example
|
||||
* mockDoVerifyEmailMutation(
|
||||
* ({ query, variables }) => {
|
||||
* const { id, code } = variables;
|
||||
* return HttpResponse.json({
|
||||
* data: { verifyEmail }
|
||||
* })
|
||||
* },
|
||||
* requestOptions
|
||||
* )
|
||||
*/
|
||||
export const mockDoVerifyEmailMutation = (resolver: GraphQLResponseResolver<DoVerifyEmailMutation, DoVerifyEmailMutationVariables>, options?: RequestHandlerOptions) =>
|
||||
graphql.mutation<DoVerifyEmailMutation, DoVerifyEmailMutationVariables>(
|
||||
'DoVerifyEmail',
|
||||
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))
|
||||
* @see https://mswjs.io/docs/basics/response-resolver
|
||||
* @example
|
||||
* mockResendVerificationEmailMutation(
|
||||
* ({ query, variables }) => {
|
||||
* const { id } = variables;
|
||||
* return HttpResponse.json({
|
||||
* data: { sendVerificationEmail }
|
||||
* })
|
||||
* },
|
||||
* requestOptions
|
||||
* )
|
||||
*/
|
||||
export const mockResendVerificationEmailMutation = (resolver: GraphQLResponseResolver<ResendVerificationEmailMutation, ResendVerificationEmailMutationVariables>, options?: RequestHandlerOptions) =>
|
||||
graphql.mutation<ResendVerificationEmailMutation, ResendVerificationEmailMutationVariables>(
|
||||
'ResendVerificationEmail',
|
||||
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))
|
||||
@@ -2928,6 +2927,50 @@ export const mockDeviceRedirectQuery = (resolver: GraphQLResponseResolver<Device
|
||||
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))
|
||||
* @see https://mswjs.io/docs/basics/response-resolver
|
||||
* @example
|
||||
* mockDoVerifyEmailMutation(
|
||||
* ({ query, variables }) => {
|
||||
* const { id, code } = variables;
|
||||
* return HttpResponse.json({
|
||||
* data: { completeEmailAuthentication }
|
||||
* })
|
||||
* },
|
||||
* requestOptions
|
||||
* )
|
||||
*/
|
||||
export const mockDoVerifyEmailMutation = (resolver: GraphQLResponseResolver<DoVerifyEmailMutation, DoVerifyEmailMutationVariables>, options?: RequestHandlerOptions) =>
|
||||
graphql.mutation<DoVerifyEmailMutation, DoVerifyEmailMutationVariables>(
|
||||
'DoVerifyEmail',
|
||||
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))
|
||||
* @see https://mswjs.io/docs/basics/response-resolver
|
||||
* @example
|
||||
* mockResendEmailAuthenticationCodeMutation(
|
||||
* ({ query, variables }) => {
|
||||
* const { id, language } = variables;
|
||||
* return HttpResponse.json({
|
||||
* data: { resendEmailAuthenticationCode }
|
||||
* })
|
||||
* },
|
||||
* requestOptions
|
||||
* )
|
||||
*/
|
||||
export const mockResendEmailAuthenticationCodeMutation = (resolver: GraphQLResponseResolver<ResendEmailAuthenticationCodeMutation, ResendEmailAuthenticationCodeMutationVariables>, options?: RequestHandlerOptions) =>
|
||||
graphql.mutation<ResendEmailAuthenticationCodeMutation, ResendEmailAuthenticationCodeMutationVariables>(
|
||||
'ResendEmailAuthenticationCode',
|
||||
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))
|
||||
@@ -2937,7 +2980,7 @@ export const mockDeviceRedirectQuery = (resolver: GraphQLResponseResolver<Device
|
||||
* ({ query, variables }) => {
|
||||
* const { id } = variables;
|
||||
* return HttpResponse.json({
|
||||
* data: { userEmail }
|
||||
* data: { userEmailAuthentication }
|
||||
* })
|
||||
* },
|
||||
* requestOptions
|
||||
|
||||
@@ -50,9 +50,7 @@ function Index(): React.ReactElement {
|
||||
>
|
||||
<UserEmailList siteConfig={siteConfig} />
|
||||
|
||||
{siteConfig.emailChangeAllowed && (
|
||||
<AddEmailForm userId={viewer.id} onAdd={onAdd} />
|
||||
)}
|
||||
{siteConfig.emailChangeAllowed && <AddEmailForm onAdd={onAdd} />}
|
||||
</Collapsible.Section>
|
||||
|
||||
<Separator kind="section" />
|
||||
|
||||
@@ -17,8 +17,6 @@ const QUERY = graphql(/* GraphQL */ `
|
||||
viewer {
|
||||
__typename
|
||||
... on User {
|
||||
id
|
||||
|
||||
emails(first: 0) {
|
||||
totalCount
|
||||
}
|
||||
|
||||
@@ -1,17 +1,42 @@
|
||||
// Copyright 2024 New Vector Ltd.
|
||||
// Copyright 2024, 2025 New Vector Ltd.
|
||||
// Copyright 2024 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// Please see LICENSE in the repository root for full details.
|
||||
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
import { createLazyFileRoute, notFound } from "@tanstack/react-router";
|
||||
|
||||
import IconArrowLeft from "@vector-im/compound-design-tokens/assets/web/icons/arrow-left";
|
||||
import IconSend from "@vector-im/compound-design-tokens/assets/web/icons/send-solid";
|
||||
import { Alert, Button, Form } from "@vector-im/compound-web";
|
||||
import { useRef } from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { ButtonLink } from "../components/ButtonLink";
|
||||
import Layout from "../components/Layout";
|
||||
import VerifyEmailComponent from "../components/VerifyEmail";
|
||||
|
||||
import LoadingSpinner from "../components/LoadingSpinner";
|
||||
import PageHeading from "../components/PageHeading";
|
||||
import { graphql } from "../gql";
|
||||
import { graphqlRequest } from "../graphql";
|
||||
import { query } from "./emails.$id.verify";
|
||||
|
||||
const VERIFY_EMAIL_MUTATION = graphql(/* GraphQL */ `
|
||||
mutation DoVerifyEmail($id: ID!, $code: String!) {
|
||||
completeEmailAuthentication(input: { id: $id, code: $code }) {
|
||||
status
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const RESEND_EMAIL_AUTHENTICATION_CODE_MUTATION = graphql(/* GraphQL */ `
|
||||
mutation ResendEmailAuthenticationCode($id: ID!, $language: String!) {
|
||||
resendEmailAuthenticationCode(input: { id: $id, language: $language }) {
|
||||
status
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export const Route = createLazyFileRoute("/emails/$id/verify")({
|
||||
component: EmailVerify,
|
||||
});
|
||||
@@ -19,13 +44,133 @@ export const Route = createLazyFileRoute("/emails/$id/verify")({
|
||||
function EmailVerify(): React.ReactElement {
|
||||
const { id } = Route.useParams();
|
||||
const {
|
||||
data: { userEmail },
|
||||
data: { userEmailAuthentication },
|
||||
} = useSuspenseQuery(query(id));
|
||||
if (!userEmail) throw notFound();
|
||||
if (!userEmailAuthentication) throw notFound();
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const navigate = useNavigate();
|
||||
const verifyEmail = useMutation({
|
||||
mutationFn: ({ id, code }: { id: string; code: string }) =>
|
||||
graphqlRequest({ query: VERIFY_EMAIL_MUTATION, variables: { id, code } }),
|
||||
async onSuccess(data): Promise<void> {
|
||||
await queryClient.invalidateQueries({ queryKey: ["userEmails"] });
|
||||
await queryClient.invalidateQueries({ queryKey: ["verifyEmail", id] });
|
||||
|
||||
if (data.completeEmailAuthentication.status === "COMPLETED") {
|
||||
await navigate({ to: "/" });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const resendEmailAuthenticationCode = useMutation({
|
||||
mutationFn: ({ id, language }: { id: string; language: string }) =>
|
||||
graphqlRequest({
|
||||
query: RESEND_EMAIL_AUTHENTICATION_CODE_MUTATION,
|
||||
variables: { id, language },
|
||||
}),
|
||||
onSuccess() {
|
||||
fieldRef.current?.focus();
|
||||
},
|
||||
});
|
||||
|
||||
const fieldRef = useRef<HTMLInputElement>(null);
|
||||
const { t, i18n } = useTranslation();
|
||||
|
||||
const onFormSubmit = (e: React.FormEvent<HTMLFormElement>): void => {
|
||||
e.preventDefault();
|
||||
const form = e.currentTarget;
|
||||
const formData = new FormData(form);
|
||||
const code = formData.get("code") as string;
|
||||
verifyEmail
|
||||
.mutateAsync({ id: userEmailAuthentication.id, code })
|
||||
.finally(() => form.reset());
|
||||
};
|
||||
|
||||
const onResendClick = (): void => {
|
||||
resendEmailAuthenticationCode.mutate({
|
||||
id: userEmailAuthentication.id,
|
||||
language: i18n.languages[0],
|
||||
});
|
||||
};
|
||||
|
||||
const emailSent =
|
||||
resendEmailAuthenticationCode.data?.resendEmailAuthenticationCode.status ===
|
||||
"RESENT";
|
||||
const invalidCode =
|
||||
verifyEmail.data?.completeEmailAuthentication.status === "INVALID_CODE";
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<VerifyEmailComponent email={userEmail} />
|
||||
<PageHeading
|
||||
Icon={IconSend}
|
||||
title={t("frontend.verify_email.heading")}
|
||||
subtitle={
|
||||
<Trans
|
||||
i18nKey="frontend.verify_email.enter_code_prompt"
|
||||
values={{ email: userEmailAuthentication.email }}
|
||||
components={{ email: <span className="text-primary" /> }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<Form.Root onSubmit={onFormSubmit}>
|
||||
{emailSent && (
|
||||
<Alert
|
||||
type="success"
|
||||
title={t("frontend.verify_email.email_sent_alert.title")}
|
||||
>
|
||||
{t("frontend.verify_email.email_sent_alert.description")}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{invalidCode && (
|
||||
<Alert
|
||||
type="critical"
|
||||
title={t("frontend.verify_email.invalid_code_alert.title")}
|
||||
>
|
||||
{t("frontend.verify_email.invalid_code_alert.description")}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Form.Field
|
||||
name="code"
|
||||
serverInvalid={invalidCode}
|
||||
className="self-center mb-4"
|
||||
>
|
||||
<Form.Label>{t("frontend.verify_email.code_field_label")}</Form.Label>
|
||||
<Form.MFAControl ref={fieldRef} />
|
||||
|
||||
{invalidCode && (
|
||||
<Form.ErrorMessage>
|
||||
{t("frontend.verify_email.code_field_error")}
|
||||
</Form.ErrorMessage>
|
||||
)}
|
||||
|
||||
<Form.ErrorMessage match="patternMismatch">
|
||||
{t("frontend.verify_email.code_field_wrong_shape")}
|
||||
</Form.ErrorMessage>
|
||||
</Form.Field>
|
||||
|
||||
<Form.Submit type="submit" disabled={verifyEmail.isPending}>
|
||||
{verifyEmail.isPending && <LoadingSpinner inline />}
|
||||
{t("action.continue")}
|
||||
</Form.Submit>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
kind="secondary"
|
||||
disabled={resendEmailAuthenticationCode.isPending}
|
||||
onClick={onResendClick}
|
||||
>
|
||||
{resendEmailAuthenticationCode.isPending && <LoadingSpinner inline />}
|
||||
{t("frontend.verify_email.resend_code")}
|
||||
</Button>
|
||||
|
||||
<ButtonLink as="a" Icon={IconArrowLeft} kind="tertiary" to="/">
|
||||
{t("action.back")}
|
||||
</ButtonLink>
|
||||
</Form.Root>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,14 +5,16 @@
|
||||
// Please see LICENSE in the repository root for full details.
|
||||
|
||||
import { queryOptions } from "@tanstack/react-query";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { createFileRoute, notFound, redirect } from "@tanstack/react-router";
|
||||
import { graphql } from "../gql";
|
||||
import { graphqlRequest } from "../graphql";
|
||||
|
||||
const QUERY = graphql(/* GraphQL */ `
|
||||
query VerifyEmail($id: ID!) {
|
||||
userEmail(id: $id) {
|
||||
...UserEmail_verifyEmail
|
||||
userEmailAuthentication(id: $id) {
|
||||
id
|
||||
email
|
||||
completedAt
|
||||
}
|
||||
}
|
||||
`);
|
||||
@@ -25,6 +27,14 @@ export const query = (id: string) =>
|
||||
});
|
||||
|
||||
export const Route = createFileRoute("/emails/$id/verify")({
|
||||
loader: ({ context, params }) =>
|
||||
context.queryClient.ensureQueryData(query(params.id)),
|
||||
async loader({ context, params }): Promise<void> {
|
||||
const data = await context.queryClient.ensureQueryData(query(params.id));
|
||||
if (!data.userEmailAuthentication) {
|
||||
throw notFound();
|
||||
}
|
||||
|
||||
if (data.userEmailAuthentication.completedAt) {
|
||||
throw redirect({ to: "/" });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -45,7 +45,6 @@ const userProfileHandler = ({
|
||||
data: {
|
||||
viewer: {
|
||||
__typename: "User",
|
||||
id: "user-id",
|
||||
emails: {
|
||||
totalCount: emailTotalCount,
|
||||
},
|
||||
|
||||
@@ -14,6 +14,7 @@ module.exports = {
|
||||
theme: {
|
||||
colors: {
|
||||
white: "#FFFFFF",
|
||||
primary: "var(--cpd-color-text-primary)",
|
||||
secondary: "var(--cpd-color-text-secondary)",
|
||||
critical: "var(--cpd-color-text-critical-primary)",
|
||||
alert: "#FF5B55",
|
||||
|
||||
@@ -93,7 +93,6 @@ export const handlers = [
|
||||
data: {
|
||||
viewer: {
|
||||
__typename: "User",
|
||||
id: "user-id",
|
||||
emails: {
|
||||
totalCount: 1,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user