You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-11-20 12:02:22 +03:00
frontend: move the email verification form to a dedicated page
This commit is contained in:
@@ -28,6 +28,7 @@ type HomeRoute = { type: "home" };
|
||||
type AccountRoute = { type: "account" };
|
||||
type OAuth2ClientRoute = { type: "client"; id: string };
|
||||
type BrowserSessionRoute = { type: "session"; id: string };
|
||||
type VerifyEmailRoute = { type: "verify-email"; id: string };
|
||||
type UnknownRoute = { type: "unknown"; segments: string[] };
|
||||
|
||||
export type Route =
|
||||
@@ -35,6 +36,7 @@ export type Route =
|
||||
| AccountRoute
|
||||
| OAuth2ClientRoute
|
||||
| BrowserSessionRoute
|
||||
| VerifyEmailRoute
|
||||
| UnknownRoute;
|
||||
|
||||
const routeToSegments = (route: Route): string[] => {
|
||||
@@ -47,6 +49,8 @@ const routeToSegments = (route: Route): string[] => {
|
||||
return ["client", route.id];
|
||||
case "session":
|
||||
return ["session", route.id];
|
||||
case "verify-email":
|
||||
return ["verify-email", route.id];
|
||||
case "unknown":
|
||||
return route.segments;
|
||||
}
|
||||
@@ -69,6 +73,10 @@ const segmentsToRoute = (segments: string[]): Route => {
|
||||
return { type: "session", id: segments[1] };
|
||||
}
|
||||
|
||||
if (segments.length === 2 && segments[0] === "verify-email") {
|
||||
return { type: "verify-email", id: segments[1] };
|
||||
}
|
||||
|
||||
return { type: "unknown", segments };
|
||||
};
|
||||
|
||||
@@ -112,6 +120,7 @@ const Home = lazy(() => import("./pages/Home"));
|
||||
const Account = lazy(() => import("./pages/Account"));
|
||||
const OAuth2Client = lazy(() => import("./pages/OAuth2Client"));
|
||||
const BrowserSession = lazy(() => import("./pages/BrowserSession"));
|
||||
const VerifyEmail = lazy(() => import("./pages/VerifyEmail"));
|
||||
|
||||
const InnerRouter: React.FC = () => {
|
||||
const route = useAtomValue(routeAtom);
|
||||
@@ -125,6 +134,8 @@ const InnerRouter: React.FC = () => {
|
||||
return <OAuth2Client id={route.id} />;
|
||||
case "session":
|
||||
return <BrowserSession id={route.id} />;
|
||||
case "verify-email":
|
||||
return <VerifyEmail id={route.id} />;
|
||||
case "unknown":
|
||||
return <>Unknown route {JSON.stringify(route.segments)}</>;
|
||||
}
|
||||
@@ -154,14 +165,10 @@ export const Link: React.FC<
|
||||
<a
|
||||
href={path}
|
||||
onClick={(e: React.MouseEvent): void => {
|
||||
// Local links should be handled by the internal routers
|
||||
// external links do not require a transition
|
||||
if (!path.startsWith("http")) {
|
||||
e.preventDefault();
|
||||
startTransition(() => {
|
||||
setRoute(route);
|
||||
});
|
||||
}
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
import { Control, Field, Root, Submit } from "@vector-im/compound-web";
|
||||
import { atom, useAtom } from "jotai";
|
||||
import { useAtom } from "jotai";
|
||||
import { atomWithMutation } from "jotai-urql";
|
||||
import { useRef, useTransition } from "react";
|
||||
|
||||
@@ -35,15 +35,10 @@ const ADD_EMAIL_MUTATION = graphql(/* GraphQL */ `
|
||||
|
||||
const addUserEmailAtom = atomWithMutation(ADD_EMAIL_MUTATION);
|
||||
|
||||
export const latestAddedEmailAtom = atom(async (get) => {
|
||||
const result = await get(addUserEmailAtom);
|
||||
return result.data?.addEmail.email?.id ?? null;
|
||||
});
|
||||
|
||||
const AddEmailForm: React.FC<{ userId: string; onAdd?: () => void }> = ({
|
||||
userId,
|
||||
onAdd,
|
||||
}) => {
|
||||
const AddEmailForm: React.FC<{
|
||||
userId: string;
|
||||
onAdd?: (id: string) => void;
|
||||
}> = ({ userId, onAdd }) => {
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
const [addEmailResult, addEmail] = useAtom(addUserEmailAtom);
|
||||
const [pending, startTransition] = useTransition();
|
||||
@@ -60,8 +55,12 @@ const AddEmailForm: React.FC<{ userId: string; onAdd?: () => void }> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.data?.addEmail.email?.id) {
|
||||
throw new Error("Unexpected response from server");
|
||||
}
|
||||
|
||||
// Call the onAdd callback if provided
|
||||
onAdd?.();
|
||||
onAdd?.(result.data?.addEmail.email?.id);
|
||||
|
||||
// Reset the form
|
||||
formRef.current?.reset();
|
||||
|
||||
@@ -1,299 +0,0 @@
|
||||
// Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {
|
||||
Button,
|
||||
Control,
|
||||
Field,
|
||||
Label,
|
||||
Message,
|
||||
Root as Form,
|
||||
Submit,
|
||||
} from "@vector-im/compound-web";
|
||||
import { atom, useAtom, useSetAtom } from "jotai";
|
||||
import { atomFamily } from "jotai/utils";
|
||||
import { atomWithMutation } from "jotai-urql";
|
||||
import { useRef, useTransition } from "react";
|
||||
|
||||
import { FragmentType, graphql, useFragment } from "../gql";
|
||||
|
||||
import Block from "./Block";
|
||||
import DateTime from "./DateTime";
|
||||
import Typography from "./Typography";
|
||||
|
||||
// This component shows a single user email address, with controls to verify it,
|
||||
// resend the verification email, remove it, and set it as the primary email address.
|
||||
|
||||
const FRAGMENT = graphql(/* GraphQL */ `
|
||||
fragment UserEmail_email on UserEmail {
|
||||
id
|
||||
email
|
||||
createdAt
|
||||
confirmedAt
|
||||
}
|
||||
`);
|
||||
|
||||
const VERIFY_EMAIL_MUTATION = graphql(/* GraphQL */ `
|
||||
mutation VerifyEmail($id: ID!, $code: String!) {
|
||||
verifyEmail(input: { userEmailId: $id, code: $code }) {
|
||||
status
|
||||
|
||||
user {
|
||||
id
|
||||
primaryEmail {
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
email {
|
||||
id
|
||||
...UserEmail_email
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const RESEND_VERIFICATION_EMAIL_MUTATION = graphql(/* GraphQL */ `
|
||||
mutation ResendVerificationEmail($id: ID!) {
|
||||
sendVerificationEmail(input: { userEmailId: $id }) {
|
||||
status
|
||||
|
||||
user {
|
||||
id
|
||||
primaryEmail {
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
email {
|
||||
id
|
||||
...UserEmail_email
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const REMOVE_EMAIL_MUTATION = graphql(/* GraphQL */ `
|
||||
mutation RemoveEmail($id: ID!) {
|
||||
removeEmail(input: { userEmailId: $id }) {
|
||||
status
|
||||
|
||||
user {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const SET_PRIMARY_EMAIL_MUTATION = graphql(/* GraphQL */ `
|
||||
mutation SetPrimaryEmail($id: ID!) {
|
||||
setPrimaryEmail(input: { userEmailId: $id }) {
|
||||
status
|
||||
user {
|
||||
id
|
||||
primaryEmail {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const verifyEmailFamily = atomFamily((id: string) => {
|
||||
const verifyEmail = atomWithMutation(VERIFY_EMAIL_MUTATION);
|
||||
|
||||
// A proxy atom which pre-sets the id variable in the mutation
|
||||
const verifyEmailAtom = atom(
|
||||
(get) => get(verifyEmail),
|
||||
(get, set, code: string) => set(verifyEmail, { id, code }),
|
||||
);
|
||||
|
||||
return verifyEmailAtom;
|
||||
});
|
||||
|
||||
const resendVerificationEmailFamily = atomFamily((id: string) => {
|
||||
const resendVerificationEmail = atomWithMutation(
|
||||
RESEND_VERIFICATION_EMAIL_MUTATION,
|
||||
);
|
||||
|
||||
// A proxy atom which pre-sets the id variable in the mutation
|
||||
const resendVerificationEmailAtom = atom(
|
||||
(get) => get(resendVerificationEmail),
|
||||
(get, set) => set(resendVerificationEmail, { id }),
|
||||
);
|
||||
|
||||
return resendVerificationEmailAtom;
|
||||
});
|
||||
|
||||
const removeEmailFamily = atomFamily((id: string) => {
|
||||
const removeEmail = atomWithMutation(REMOVE_EMAIL_MUTATION);
|
||||
|
||||
// A proxy atom which pre-sets the id variable in the mutation
|
||||
const removeEmailAtom = atom(
|
||||
(get) => get(removeEmail),
|
||||
(get, set) => set(removeEmail, { id }),
|
||||
);
|
||||
|
||||
return removeEmailAtom;
|
||||
});
|
||||
|
||||
const setPrimaryEmailFamily = atomFamily((id: string) => {
|
||||
const setPrimaryEmail = atomWithMutation(SET_PRIMARY_EMAIL_MUTATION);
|
||||
|
||||
// A proxy atom which pre-sets the id variable in the mutation
|
||||
const setPrimaryEmailAtom = atom(
|
||||
(get) => get(setPrimaryEmail),
|
||||
(get, set) => set(setPrimaryEmail, { id }),
|
||||
);
|
||||
|
||||
return setPrimaryEmailAtom;
|
||||
});
|
||||
|
||||
const UserEmail: React.FC<{
|
||||
email: FragmentType<typeof FRAGMENT>;
|
||||
onRemove?: () => void;
|
||||
onSetPrimary?: () => void;
|
||||
isPrimary?: boolean;
|
||||
highlight?: boolean;
|
||||
}> = ({ email, isPrimary, highlight, onSetPrimary, onRemove }) => {
|
||||
const [pending, startTransition] = useTransition();
|
||||
const data = useFragment(FRAGMENT, email);
|
||||
const [verifyEmailResult, verifyEmail] = useAtom(verifyEmailFamily(data.id));
|
||||
const [resendVerificationEmailResult, resendVerificationEmail] = useAtom(
|
||||
resendVerificationEmailFamily(data.id),
|
||||
);
|
||||
const setPrimaryEmail = useSetAtom(setPrimaryEmailFamily(data.id));
|
||||
const removeEmail = useSetAtom(removeEmailFamily(data.id));
|
||||
const fieldRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const onFormSubmit = (e: React.FormEvent<HTMLFormElement>): void => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.currentTarget);
|
||||
const code = formData.get("code") as string;
|
||||
startTransition(() => {
|
||||
verifyEmail(code).then((result) => {
|
||||
// Clear the form
|
||||
e.currentTarget?.reset();
|
||||
|
||||
if (result.data?.verifyEmail.status === "VERIFIED") {
|
||||
// Call the onSetPrimary callback if provided
|
||||
// XXX: do we need a dedicated onVerify callback?
|
||||
onSetPrimary?.();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const onResendClick = (): void => {
|
||||
startTransition(() => {
|
||||
resendVerificationEmail().then(() => {
|
||||
fieldRef.current?.focus();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const onRemoveClick = (): void => {
|
||||
startTransition(() => {
|
||||
removeEmail().then(() => {
|
||||
// Call the onRemove callback if provided
|
||||
onRemove?.();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const onSetPrimaryClick = (): void => {
|
||||
startTransition(() => {
|
||||
setPrimaryEmail().then(() => {
|
||||
// Call the onSetPrimary callback if provided
|
||||
onSetPrimary?.();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const emailSent =
|
||||
resendVerificationEmailResult.data?.sendVerificationEmail.status === "SENT";
|
||||
const invalidCode =
|
||||
verifyEmailResult.data?.verifyEmail.status === "INVALID_CODE";
|
||||
|
||||
return (
|
||||
<Block
|
||||
highlight={highlight}
|
||||
className="grid grid-col-1 gap-2 pb-4 border-b-2 border-b-grey-200"
|
||||
>
|
||||
{isPrimary && (
|
||||
<Typography variant="body" bold>
|
||||
Primary
|
||||
</Typography>
|
||||
)}
|
||||
<Typography variant="caption" bold className="flex-1">
|
||||
{data.email}
|
||||
</Typography>
|
||||
{data.confirmedAt ? (
|
||||
<Typography variant="micro">
|
||||
Verified <DateTime datetime={data.confirmedAt} />
|
||||
</Typography>
|
||||
) : (
|
||||
<Form onSubmit={onFormSubmit} className="grid grid-cols-2 gap-2">
|
||||
<Field name="code" className="col-span-2">
|
||||
<Label>Code</Label>
|
||||
<Control
|
||||
ref={fieldRef}
|
||||
placeholder="xxxxxx"
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
/>
|
||||
</Field>
|
||||
{invalidCode && (
|
||||
<Message className="col-span-2 text-alert font-bold">
|
||||
Invalid code
|
||||
</Message>
|
||||
)}
|
||||
<Submit size="sm" type="submit" disabled={pending}>
|
||||
Submit
|
||||
</Submit>
|
||||
<Button
|
||||
size="sm"
|
||||
kind="secondary"
|
||||
disabled={pending || emailSent}
|
||||
onClick={onResendClick}
|
||||
>
|
||||
{emailSent ? "Sent!" : "Resend"}
|
||||
</Button>
|
||||
</Form>
|
||||
)}
|
||||
{!isPrimary && (
|
||||
<div className="flex justify-between items-center">
|
||||
{/* The primary email can only be set if the email was verified */}
|
||||
{data.confirmedAt ? (
|
||||
<Button size="sm" disabled={pending} onClick={onSetPrimaryClick}>
|
||||
Set primary
|
||||
</Button>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
<Button
|
||||
kind="destructive"
|
||||
size="sm"
|
||||
disabled={pending}
|
||||
onClick={onRemoveClick}
|
||||
>
|
||||
Remove
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Block>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserEmail;
|
||||
81
frontend/src/components/UserEmail/UserEmail.module.css
Normal file
81
frontend/src/components/UserEmail/UserEmail.module.css
Normal file
@@ -0,0 +1,81 @@
|
||||
/* Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.user-email {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: var(--cpd-space-2x);
|
||||
}
|
||||
|
||||
.user-email-line {
|
||||
display: flex;
|
||||
align-self: stretch;
|
||||
gap: var(--cpd-space-2x);
|
||||
}
|
||||
|
||||
.user-email-field {
|
||||
border: 1px solid var(--cpd-color-border-interactive-primary);
|
||||
background: var(--cpd-color-bg-canvas-default);
|
||||
border-radius: 0.5rem;
|
||||
padding: var(--cpd-space-3x) var(--cpd-space-4x);
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.user-email-delete {
|
||||
box-sizing: border-box;
|
||||
height: var(--cpd-space-12x);
|
||||
width: var(--cpd-space-12x);
|
||||
border-radius: 50%;
|
||||
color: var(--cpd-color-icon-critical-primary);
|
||||
padding: var(--cpd-space-2x);
|
||||
}
|
||||
|
||||
.user-email-delete:hover:not([disabled]) {
|
||||
background-color: var(--cpd-color-red-300);
|
||||
}
|
||||
|
||||
.user-email-delete[disabled] {
|
||||
color: var(--cpd-color-icon-disabled);
|
||||
}
|
||||
|
||||
.user-email-delete-icon {
|
||||
display: block;
|
||||
height: var(--cpd-space-8x);
|
||||
width: var(--cpd-space-8x);
|
||||
}
|
||||
|
||||
.user-email-unverified {
|
||||
color: var(--cpd-color-text-critical-primary);
|
||||
}
|
||||
|
||||
.link {
|
||||
display: inline-block;
|
||||
text-decoration: underline;
|
||||
color: var(--cpd-color-text-primary);
|
||||
font-weight: var(--cpd-font-weight-medium);
|
||||
border-radius: var(--cpd-radius-pill-effect);
|
||||
padding-inline: 0.25rem;
|
||||
}
|
||||
|
||||
.link:hover {
|
||||
background: var(--cpd-color-gray-300);
|
||||
}
|
||||
|
||||
.link:active {
|
||||
color: var(--cpd-color-text-on-solid-primary);
|
||||
}
|
||||
|
||||
164
frontend/src/components/UserEmail/UserEmail.tsx
Normal file
164
frontend/src/components/UserEmail/UserEmail.tsx
Normal file
@@ -0,0 +1,164 @@
|
||||
// Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import IconDelete from "@vector-im/compound-design-tokens/icons/delete.svg";
|
||||
import { Body } from "@vector-im/compound-web";
|
||||
import { atom, useSetAtom } from "jotai";
|
||||
import { atomFamily } from "jotai/utils";
|
||||
import { atomWithMutation } from "jotai-urql";
|
||||
import { useTransition } from "react";
|
||||
|
||||
import { Link } from "../../Router";
|
||||
import { FragmentType, graphql, useFragment } from "../../gql";
|
||||
|
||||
import styles from "./UserEmail.module.css";
|
||||
|
||||
// This component shows a single user email address, with controls to verify it,
|
||||
// resend the verification email, remove it, and set it as the primary email address.
|
||||
|
||||
const FRAGMENT = graphql(/* GraphQL */ `
|
||||
fragment UserEmail_email on UserEmail {
|
||||
id
|
||||
email
|
||||
confirmedAt
|
||||
}
|
||||
`);
|
||||
|
||||
const REMOVE_EMAIL_MUTATION = graphql(/* GraphQL */ `
|
||||
mutation RemoveEmail($id: ID!) {
|
||||
removeEmail(input: { userEmailId: $id }) {
|
||||
status
|
||||
|
||||
user {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const SET_PRIMARY_EMAIL_MUTATION = graphql(/* GraphQL */ `
|
||||
mutation SetPrimaryEmail($id: ID!) {
|
||||
setPrimaryEmail(input: { userEmailId: $id }) {
|
||||
status
|
||||
user {
|
||||
id
|
||||
primaryEmail {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const removeEmailFamily = atomFamily((id: string) => {
|
||||
const removeEmail = atomWithMutation(REMOVE_EMAIL_MUTATION);
|
||||
|
||||
// A proxy atom which pre-sets the id variable in the mutation
|
||||
const removeEmailAtom = atom(
|
||||
(get) => get(removeEmail),
|
||||
(_get, set) => set(removeEmail, { id }),
|
||||
);
|
||||
|
||||
return removeEmailAtom;
|
||||
});
|
||||
|
||||
const setPrimaryEmailFamily = atomFamily((id: string) => {
|
||||
const setPrimaryEmail = atomWithMutation(SET_PRIMARY_EMAIL_MUTATION);
|
||||
|
||||
// A proxy atom which pre-sets the id variable in the mutation
|
||||
const setPrimaryEmailAtom = atom(
|
||||
(get) => get(setPrimaryEmail),
|
||||
(_get, set) => set(setPrimaryEmail, { id }),
|
||||
);
|
||||
|
||||
return setPrimaryEmailAtom;
|
||||
});
|
||||
|
||||
const DeleteButton: React.FC<{ disabled?: boolean; onClick?: () => void }> = ({
|
||||
disabled,
|
||||
onClick,
|
||||
}) => (
|
||||
<button
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
className={styles.userEmailDelete}
|
||||
title="Remove email address"
|
||||
>
|
||||
<IconDelete className={styles.userEmailDeleteIcon} />
|
||||
</button>
|
||||
);
|
||||
|
||||
const UserEmail: React.FC<{
|
||||
email: FragmentType<typeof FRAGMENT>;
|
||||
onRemove?: () => void;
|
||||
onSetPrimary?: () => void;
|
||||
isPrimary?: boolean;
|
||||
highlight?: boolean;
|
||||
}> = ({ email, isPrimary, highlight, onSetPrimary, onRemove }) => {
|
||||
const [pending, startTransition] = useTransition();
|
||||
const data = useFragment(FRAGMENT, email);
|
||||
const setPrimaryEmail = useSetAtom(setPrimaryEmailFamily(data.id));
|
||||
const removeEmail = useSetAtom(removeEmailFamily(data.id));
|
||||
|
||||
const onRemoveClick = (): void => {
|
||||
startTransition(() => {
|
||||
removeEmail().then(() => {
|
||||
// Call the onRemove callback if provided
|
||||
onRemove?.();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const onSetPrimaryClick = (): void => {
|
||||
startTransition(() => {
|
||||
setPrimaryEmail().then(() => {
|
||||
// Call the onSetPrimary callback if provided
|
||||
onSetPrimary?.();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.userEmail}>
|
||||
{isPrimary ? <Body>Primary email</Body> : <Body>Email</Body>}
|
||||
|
||||
<div className={styles.userEmailLine}>
|
||||
<div className={styles.userEmailField}>{data.email}</div>
|
||||
<DeleteButton disabled={isPrimary || pending} onClick={onRemoveClick} />
|
||||
</div>
|
||||
{data.confirmedAt && !isPrimary && (
|
||||
<button
|
||||
className={styles.link}
|
||||
disabled={pending}
|
||||
onClick={onSetPrimaryClick}
|
||||
>
|
||||
Make primary
|
||||
</button>
|
||||
)}
|
||||
{!data.confirmedAt && (
|
||||
<div>
|
||||
<span className={styles.userEmailUnverified}>Unverified</span> |{" "}
|
||||
<Link
|
||||
className={styles.link}
|
||||
route={{ type: "verify-email", id: data.id }}
|
||||
>
|
||||
Retry verification
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserEmail;
|
||||
15
frontend/src/components/UserEmail/index.ts
Normal file
15
frontend/src/components/UserEmail/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
export { default } from "./UserEmail";
|
||||
@@ -17,17 +17,17 @@ import { atomFamily } from "jotai/utils";
|
||||
import { atomWithQuery } from "jotai-urql";
|
||||
import { useTransition } from "react";
|
||||
|
||||
import { routeAtom } from "../Router";
|
||||
import { graphql } from "../gql";
|
||||
import { PageInfo } from "../gql/graphql";
|
||||
import {
|
||||
atomForCurrentPagination,
|
||||
atomWithPagination,
|
||||
FIRST_PAGE,
|
||||
LAST_PAGE,
|
||||
Pagination,
|
||||
} from "../pagination";
|
||||
|
||||
import AddEmailForm, { latestAddedEmailAtom } from "./AddEmailForm";
|
||||
import AddEmailForm from "./AddEmailForm";
|
||||
import BlockList from "./BlockList";
|
||||
import PaginationControls from "./PaginationControls";
|
||||
import UserEmail from "./UserEmail";
|
||||
@@ -129,12 +129,11 @@ const UserEmailList: React.FC<{
|
||||
const [pending, startTransition] = useTransition();
|
||||
const [result, refreshList] = useAtom(emailPageResultFamily(userId));
|
||||
const setPagination = useSetAtom(currentPaginationAtom);
|
||||
const setRoute = useSetAtom(routeAtom);
|
||||
const [prevPage, nextPage] = useAtomValue(paginationFamily(userId));
|
||||
const [primaryEmailId, refreshPrimaryEmailId] = useAtom(
|
||||
primaryEmailIdFamily(userId),
|
||||
);
|
||||
// XXX: we may not want to directly use that atom here, but rather have a local state
|
||||
const latestAddedEmail = useAtomValue(latestAddedEmailAtom);
|
||||
|
||||
const paginate = (pagination: Pagination): void => {
|
||||
startTransition(() => {
|
||||
@@ -150,12 +149,9 @@ const UserEmailList: React.FC<{
|
||||
});
|
||||
};
|
||||
|
||||
// When adding an email, we want to refresh the list and go to the last page
|
||||
const onAdd = (): void => {
|
||||
startTransition(() => {
|
||||
setPagination(LAST_PAGE);
|
||||
refreshList();
|
||||
});
|
||||
// When adding an email, we want to go to the email verification form
|
||||
const onAdd = (id: string): void => {
|
||||
setRoute({ type: "verify-email", id });
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -173,7 +169,6 @@ const UserEmailList: React.FC<{
|
||||
isPrimary={primaryEmailId === edge.node.id}
|
||||
onSetPrimary={refreshPrimaryEmailId}
|
||||
onRemove={onRemove}
|
||||
highlight={latestAddedEmail === edge.node.id}
|
||||
/>
|
||||
))}
|
||||
<AddEmailForm userId={userId} onAdd={onAdd} />
|
||||
|
||||
57
frontend/src/components/VerifyEmail/VerifyEmail.module.css
Normal file
57
frontend/src/components/VerifyEmail/VerifyEmail.module.css
Normal file
@@ -0,0 +1,57 @@
|
||||
/* Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.header {
|
||||
margin: var(--cpd-space-8x) 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font: var(--cpd-font-heading-xl-semibold);
|
||||
line-height: var(--cpd-font-line-height-regular);
|
||||
color: var(--cpd-color-text-primary);
|
||||
}
|
||||
|
||||
.tagline {
|
||||
font: var(--cpd-font-body-lg-regular);
|
||||
line-height: var(--cpd-font-line-height-regular);
|
||||
color: var(--cpd-color-text-secondary);
|
||||
margin-top: var(--cpd-space-2x);
|
||||
}
|
||||
|
||||
.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: var(--cpd-space-4x) auto;
|
||||
}
|
||||
|
||||
.email {
|
||||
font: var(--cpd-font-body-lg-semibold);
|
||||
}
|
||||
|
||||
.form {
|
||||
margin: var(--cpd-space-8x) 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--cpd-space-6x);
|
||||
}
|
||||
|
||||
.submit-button {
|
||||
display: block;
|
||||
}
|
||||
199
frontend/src/components/VerifyEmail/VerifyEmail.tsx
Normal file
199
frontend/src/components/VerifyEmail/VerifyEmail.tsx
Normal file
@@ -0,0 +1,199 @@
|
||||
// Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import IconSend from "@vector-im/compound-design-tokens/icons/check.svg";
|
||||
import {
|
||||
Button,
|
||||
Control,
|
||||
Field,
|
||||
Label,
|
||||
Submit,
|
||||
Root as Form,
|
||||
Alert,
|
||||
} from "@vector-im/compound-web";
|
||||
import { useSetAtom, atom, useAtom } from "jotai";
|
||||
import { atomFamily } from "jotai/utils";
|
||||
import { atomWithMutation } from "jotai-urql";
|
||||
import { useEffect, useRef, useTransition } from "react";
|
||||
|
||||
import { routeAtom } from "../../Router";
|
||||
import { FragmentType, graphql, useFragment } from "../../gql";
|
||||
|
||||
import styles from "./VerifyEmail.module.css";
|
||||
|
||||
const FRAGMENT = graphql(/* GraphQL */ `
|
||||
fragment UserEmail_verifyEmail on UserEmail {
|
||||
id
|
||||
email
|
||||
}
|
||||
`);
|
||||
|
||||
const VERIFY_EMAIL_MUTATION = graphql(/* GraphQL */ `
|
||||
mutation VerifyEmail($id: ID!, $code: String!) {
|
||||
verifyEmail(input: { userEmailId: $id, code: $code }) {
|
||||
status
|
||||
|
||||
user {
|
||||
id
|
||||
primaryEmail {
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
email {
|
||||
id
|
||||
...UserEmail_email
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const RESEND_VERIFICATION_EMAIL_MUTATION = graphql(/* GraphQL */ `
|
||||
mutation ResendVerificationEmail($id: ID!) {
|
||||
sendVerificationEmail(input: { userEmailId: $id }) {
|
||||
status
|
||||
|
||||
user {
|
||||
id
|
||||
primaryEmail {
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
email {
|
||||
id
|
||||
...UserEmail_email
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const verifyEmailFamily = atomFamily((id: string) => {
|
||||
const verifyEmail = atomWithMutation(VERIFY_EMAIL_MUTATION);
|
||||
|
||||
// A proxy atom which pre-sets the id variable in the mutation
|
||||
const verifyEmailAtom = atom(
|
||||
(get) => get(verifyEmail),
|
||||
(get, set, code: string) => set(verifyEmail, { id, code }),
|
||||
);
|
||||
|
||||
return verifyEmailAtom;
|
||||
});
|
||||
|
||||
const resendVerificationEmailFamily = atomFamily((id: string) => {
|
||||
const resendVerificationEmail = atomWithMutation(
|
||||
RESEND_VERIFICATION_EMAIL_MUTATION,
|
||||
);
|
||||
|
||||
// A proxy atom which pre-sets the id variable in the mutation
|
||||
const resendVerificationEmailAtom = atom(
|
||||
(get) => get(resendVerificationEmail),
|
||||
(_get, set) => set(resendVerificationEmail, { id }),
|
||||
);
|
||||
|
||||
return resendVerificationEmailAtom;
|
||||
});
|
||||
|
||||
const VerifyEmail: React.FC<{
|
||||
email: FragmentType<typeof FRAGMENT>;
|
||||
}> = ({ email }) => {
|
||||
const data = useFragment(FRAGMENT, email);
|
||||
const [pending, startTransition] = useTransition();
|
||||
const [verifyEmailResult, verifyEmail] = useAtom(verifyEmailFamily(data.id));
|
||||
const [resendVerificationEmailResult, resendVerificationEmail] = useAtom(
|
||||
resendVerificationEmailFamily(data.id),
|
||||
);
|
||||
const setRoute = useSetAtom(routeAtom);
|
||||
const fieldRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const onFormSubmit = (e: React.FormEvent<HTMLFormElement>): void => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.currentTarget);
|
||||
const code = formData.get("code") as string;
|
||||
startTransition(() => {
|
||||
verifyEmail(code).then((result) => {
|
||||
// Clear the form
|
||||
e.currentTarget?.reset();
|
||||
|
||||
if (result.data?.verifyEmail.status === "VERIFIED") {
|
||||
setRoute({ type: "account" });
|
||||
} else {
|
||||
fieldRef.current?.focus();
|
||||
fieldRef.current?.select();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Focus the field on mount
|
||||
useEffect(() => {
|
||||
fieldRef.current?.focus();
|
||||
}, [fieldRef]);
|
||||
|
||||
const onResendClick = (): void => {
|
||||
startTransition(() => {
|
||||
resendVerificationEmail().then(() => {
|
||||
fieldRef.current?.focus();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const emailSent =
|
||||
resendVerificationEmailResult.data?.sendVerificationEmail.status === "SENT";
|
||||
const invalidCode =
|
||||
verifyEmailResult.data?.verifyEmail.status === "INVALID_CODE";
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className={styles.header}>
|
||||
<IconSend className={styles.icon} />
|
||||
<h1 className={styles.title}>Verify your email</h1>
|
||||
<p className={styles.tagline}>
|
||||
Enter the 6-digit code sent to{" "}
|
||||
<span className={styles.email}>{data.email}</span>
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<Form onSubmit={onFormSubmit} className={styles.form}>
|
||||
{invalidCode && <Alert type="critical" title="Invalid code" />}
|
||||
<Field name="code" serverInvalid={invalidCode}>
|
||||
<Label>6-digit code</Label>
|
||||
<Control
|
||||
ref={fieldRef}
|
||||
placeholder="xxxxxx"
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Submit
|
||||
type="submit"
|
||||
disabled={pending}
|
||||
className={styles.submitButton}
|
||||
>
|
||||
Continue
|
||||
</Submit>
|
||||
<Button
|
||||
kind="tertiary"
|
||||
disabled={pending || emailSent}
|
||||
onClick={onResendClick}
|
||||
>
|
||||
{emailSent ? "Sent!" : "Resend email"}
|
||||
</Button>
|
||||
</Form>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default VerifyEmail;
|
||||
15
frontend/src/components/VerifyEmail/index.ts
Normal file
15
frontend/src/components/VerifyEmail/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
export { default } from "./VerifyEmail";
|
||||
@@ -39,12 +39,8 @@ const documents = {
|
||||
types.EndOAuth2SessionDocument,
|
||||
"\n query OAuth2SessionListQuery(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n oauth2Sessions(\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n cursor\n node {\n id\n ...OAuth2Session_session\n }\n }\n\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n":
|
||||
types.OAuth2SessionListQueryDocument,
|
||||
"\n fragment UserEmail_email on UserEmail {\n id\n email\n createdAt\n confirmedAt\n }\n":
|
||||
"\n fragment UserEmail_email on UserEmail {\n id\n email\n confirmedAt\n }\n":
|
||||
types.UserEmail_EmailFragmentDoc,
|
||||
"\n mutation VerifyEmail($id: ID!, $code: String!) {\n verifyEmail(input: { userEmailId: $id, code: $code }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n":
|
||||
types.VerifyEmailDocument,
|
||||
"\n mutation ResendVerificationEmail($id: ID!) {\n sendVerificationEmail(input: { userEmailId: $id }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n":
|
||||
types.ResendVerificationEmailDocument,
|
||||
"\n mutation RemoveEmail($id: ID!) {\n removeEmail(input: { userEmailId: $id }) {\n status\n\n user {\n id\n }\n }\n }\n":
|
||||
types.RemoveEmailDocument,
|
||||
"\n mutation SetPrimaryEmail($id: ID!) {\n setPrimaryEmail(input: { userEmailId: $id }) {\n status\n user {\n id\n primaryEmail {\n id\n }\n }\n }\n }\n":
|
||||
@@ -55,10 +51,18 @@ const documents = {
|
||||
types.UserPrimaryEmailDocument,
|
||||
"\n query UserGreeting($userId: ID!) {\n user(id: $userId) {\n id\n username\n matrix {\n mxid\n displayName\n }\n }\n }\n":
|
||||
types.UserGreetingDocument,
|
||||
"\n fragment UserEmail_verifyEmail on UserEmail {\n id\n email\n }\n":
|
||||
types.UserEmail_VerifyEmailFragmentDoc,
|
||||
"\n mutation VerifyEmail($id: ID!, $code: String!) {\n verifyEmail(input: { userEmailId: $id, code: $code }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n":
|
||||
types.VerifyEmailDocument,
|
||||
"\n mutation ResendVerificationEmail($id: ID!) {\n sendVerificationEmail(input: { userEmailId: $id }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n":
|
||||
types.ResendVerificationEmailDocument,
|
||||
"\n query BrowserSessionQuery($id: ID!) {\n browserSession(id: $id) {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n }\n":
|
||||
types.BrowserSessionQueryDocument,
|
||||
"\n query OAuth2ClientQuery($id: ID!) {\n oauth2Client(id: $id) {\n id\n clientId\n clientName\n clientUri\n tosUri\n policyUri\n redirectUris\n }\n }\n":
|
||||
types.OAuth2ClientQueryDocument,
|
||||
"\n query VerifyEmailQuery($id: ID!) {\n userEmail(id: $id) {\n ...UserEmail_verifyEmail\n }\n }\n":
|
||||
types.VerifyEmailQueryDocument,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -157,20 +161,8 @@ export function graphql(
|
||||
* 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_email on UserEmail {\n id\n email\n createdAt\n confirmedAt\n }\n",
|
||||
): (typeof documents)["\n fragment UserEmail_email on UserEmail {\n id\n email\n createdAt\n confirmedAt\n }\n"];
|
||||
/**
|
||||
* 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 VerifyEmail($id: ID!, $code: String!) {\n verifyEmail(input: { userEmailId: $id, code: $code }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n",
|
||||
): (typeof documents)["\n mutation VerifyEmail($id: ID!, $code: String!) {\n verifyEmail(input: { userEmailId: $id, code: $code }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n"];
|
||||
/**
|
||||
* 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 user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n",
|
||||
): (typeof documents)["\n mutation ResendVerificationEmail($id: ID!) {\n sendVerificationEmail(input: { userEmailId: $id }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n"];
|
||||
source: "\n fragment UserEmail_email on UserEmail {\n id\n email\n confirmedAt\n }\n",
|
||||
): (typeof documents)["\n fragment UserEmail_email on UserEmail {\n id\n email\n confirmedAt\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -201,6 +193,24 @@ export function graphql(
|
||||
export function graphql(
|
||||
source: "\n query UserGreeting($userId: ID!) {\n user(id: $userId) {\n id\n username\n matrix {\n mxid\n displayName\n }\n }\n }\n",
|
||||
): (typeof documents)["\n query UserGreeting($userId: ID!) {\n user(id: $userId) {\n id\n username\n matrix {\n mxid\n displayName\n }\n }\n }\n"];
|
||||
/**
|
||||
* 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 documents)["\n fragment UserEmail_verifyEmail on UserEmail {\n id\n email\n }\n"];
|
||||
/**
|
||||
* 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 VerifyEmail($id: ID!, $code: String!) {\n verifyEmail(input: { userEmailId: $id, code: $code }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n",
|
||||
): (typeof documents)["\n mutation VerifyEmail($id: ID!, $code: String!) {\n verifyEmail(input: { userEmailId: $id, code: $code }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n"];
|
||||
/**
|
||||
* 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 user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n",
|
||||
): (typeof documents)["\n mutation ResendVerificationEmail($id: ID!) {\n sendVerificationEmail(input: { userEmailId: $id }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -213,6 +223,12 @@ export function graphql(
|
||||
export function graphql(
|
||||
source: "\n query OAuth2ClientQuery($id: ID!) {\n oauth2Client(id: $id) {\n id\n clientId\n clientName\n clientUri\n tosUri\n policyUri\n redirectUris\n }\n }\n",
|
||||
): (typeof documents)["\n query OAuth2ClientQuery($id: ID!) {\n oauth2Client(id: $id) {\n id\n clientId\n clientName\n clientUri\n tosUri\n policyUri\n redirectUris\n }\n }\n"];
|
||||
/**
|
||||
* 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 VerifyEmailQuery($id: ID!) {\n userEmail(id: $id) {\n ...UserEmail_verifyEmail\n }\n }\n",
|
||||
): (typeof documents)["\n query VerifyEmailQuery($id: ID!) {\n userEmail(id: $id) {\n ...UserEmail_verifyEmail\n }\n }\n"];
|
||||
|
||||
export function graphql(source: string) {
|
||||
return (documents as any)[source] ?? {};
|
||||
|
||||
@@ -1128,55 +1128,9 @@ export type UserEmail_EmailFragment = {
|
||||
__typename?: "UserEmail";
|
||||
id: string;
|
||||
email: string;
|
||||
createdAt: any;
|
||||
confirmedAt?: any | null;
|
||||
} & { " $fragmentName"?: "UserEmail_EmailFragment" };
|
||||
|
||||
export type VerifyEmailMutationVariables = Exact<{
|
||||
id: Scalars["ID"]["input"];
|
||||
code: Scalars["String"]["input"];
|
||||
}>;
|
||||
|
||||
export type VerifyEmailMutation = {
|
||||
__typename?: "Mutation";
|
||||
verifyEmail: {
|
||||
__typename?: "VerifyEmailPayload";
|
||||
status: VerifyEmailStatus;
|
||||
user?: {
|
||||
__typename?: "User";
|
||||
id: string;
|
||||
primaryEmail?: { __typename?: "UserEmail"; id: string } | null;
|
||||
} | null;
|
||||
email?:
|
||||
| ({ __typename?: "UserEmail"; id: string } & {
|
||||
" $fragmentRefs"?: {
|
||||
UserEmail_EmailFragment: UserEmail_EmailFragment;
|
||||
};
|
||||
})
|
||||
| null;
|
||||
};
|
||||
};
|
||||
|
||||
export type ResendVerificationEmailMutationVariables = Exact<{
|
||||
id: Scalars["ID"]["input"];
|
||||
}>;
|
||||
|
||||
export type ResendVerificationEmailMutation = {
|
||||
__typename?: "Mutation";
|
||||
sendVerificationEmail: {
|
||||
__typename?: "SendVerificationEmailPayload";
|
||||
status: SendVerificationEmailStatus;
|
||||
user: {
|
||||
__typename?: "User";
|
||||
id: string;
|
||||
primaryEmail?: { __typename?: "UserEmail"; id: string } | null;
|
||||
};
|
||||
email: { __typename?: "UserEmail"; id: string } & {
|
||||
" $fragmentRefs"?: { UserEmail_EmailFragment: UserEmail_EmailFragment };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export type RemoveEmailMutationVariables = Exact<{
|
||||
id: Scalars["ID"]["input"];
|
||||
}>;
|
||||
@@ -1274,6 +1228,57 @@ export type UserGreetingQuery = {
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type UserEmail_VerifyEmailFragment = {
|
||||
__typename?: "UserEmail";
|
||||
id: string;
|
||||
email: string;
|
||||
} & { " $fragmentName"?: "UserEmail_VerifyEmailFragment" };
|
||||
|
||||
export type VerifyEmailMutationVariables = Exact<{
|
||||
id: Scalars["ID"]["input"];
|
||||
code: Scalars["String"]["input"];
|
||||
}>;
|
||||
|
||||
export type VerifyEmailMutation = {
|
||||
__typename?: "Mutation";
|
||||
verifyEmail: {
|
||||
__typename?: "VerifyEmailPayload";
|
||||
status: VerifyEmailStatus;
|
||||
user?: {
|
||||
__typename?: "User";
|
||||
id: string;
|
||||
primaryEmail?: { __typename?: "UserEmail"; id: string } | null;
|
||||
} | null;
|
||||
email?:
|
||||
| ({ __typename?: "UserEmail"; id: string } & {
|
||||
" $fragmentRefs"?: {
|
||||
UserEmail_EmailFragment: UserEmail_EmailFragment;
|
||||
};
|
||||
})
|
||||
| null;
|
||||
};
|
||||
};
|
||||
|
||||
export type ResendVerificationEmailMutationVariables = Exact<{
|
||||
id: Scalars["ID"]["input"];
|
||||
}>;
|
||||
|
||||
export type ResendVerificationEmailMutation = {
|
||||
__typename?: "Mutation";
|
||||
sendVerificationEmail: {
|
||||
__typename?: "SendVerificationEmailPayload";
|
||||
status: SendVerificationEmailStatus;
|
||||
user: {
|
||||
__typename?: "User";
|
||||
id: string;
|
||||
primaryEmail?: { __typename?: "UserEmail"; id: string } | null;
|
||||
};
|
||||
email: { __typename?: "UserEmail"; id: string } & {
|
||||
" $fragmentRefs"?: { UserEmail_EmailFragment: UserEmail_EmailFragment };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export type BrowserSessionQueryQueryVariables = Exact<{
|
||||
id: Scalars["ID"]["input"];
|
||||
}>;
|
||||
@@ -1311,6 +1316,21 @@ export type OAuth2ClientQueryQuery = {
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type VerifyEmailQueryQueryVariables = Exact<{
|
||||
id: Scalars["ID"]["input"];
|
||||
}>;
|
||||
|
||||
export type VerifyEmailQueryQuery = {
|
||||
__typename?: "Query";
|
||||
userEmail?:
|
||||
| ({ __typename?: "UserEmail" } & {
|
||||
" $fragmentRefs"?: {
|
||||
UserEmail_VerifyEmailFragment: UserEmail_VerifyEmailFragment;
|
||||
};
|
||||
})
|
||||
| null;
|
||||
};
|
||||
|
||||
export const BrowserSession_SessionFragmentDoc = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
@@ -1463,13 +1483,32 @@ export const UserEmail_EmailFragmentDoc = {
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "email" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "confirmedAt" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<UserEmail_EmailFragment, unknown>;
|
||||
export const UserEmail_VerifyEmailFragmentDoc = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
{
|
||||
kind: "FragmentDefinition",
|
||||
name: { kind: "Name", value: "UserEmail_verifyEmail" },
|
||||
typeCondition: {
|
||||
kind: "NamedType",
|
||||
name: { kind: "Name", value: "UserEmail" },
|
||||
},
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "email" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<UserEmail_VerifyEmailFragment, unknown>;
|
||||
export const CurrentViewerQueryDocument = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
@@ -1681,7 +1720,6 @@ export const AddEmailDocument = {
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "email" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "confirmedAt" } },
|
||||
],
|
||||
},
|
||||
@@ -2615,244 +2653,6 @@ export const OAuth2SessionListQueryDocument = {
|
||||
OAuth2SessionListQueryQuery,
|
||||
OAuth2SessionListQueryQueryVariables
|
||||
>;
|
||||
export const VerifyEmailDocument = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
{
|
||||
kind: "OperationDefinition",
|
||||
operation: "mutation",
|
||||
name: { kind: "Name", value: "VerifyEmail" },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: "VariableDefinition",
|
||||
variable: { kind: "Variable", name: { kind: "Name", value: "id" } },
|
||||
type: {
|
||||
kind: "NonNullType",
|
||||
type: { kind: "NamedType", name: { kind: "Name", value: "ID" } },
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "VariableDefinition",
|
||||
variable: { kind: "Variable", name: { kind: "Name", value: "code" } },
|
||||
type: {
|
||||
kind: "NonNullType",
|
||||
type: {
|
||||
kind: "NamedType",
|
||||
name: { kind: "Name", value: "String" },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "verifyEmail" },
|
||||
arguments: [
|
||||
{
|
||||
kind: "Argument",
|
||||
name: { kind: "Name", value: "input" },
|
||||
value: {
|
||||
kind: "ObjectValue",
|
||||
fields: [
|
||||
{
|
||||
kind: "ObjectField",
|
||||
name: { kind: "Name", value: "userEmailId" },
|
||||
value: {
|
||||
kind: "Variable",
|
||||
name: { kind: "Name", value: "id" },
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "ObjectField",
|
||||
name: { kind: "Name", value: "code" },
|
||||
value: {
|
||||
kind: "Variable",
|
||||
name: { kind: "Name", value: "code" },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "status" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "primaryEmail" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "id" },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "email" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{
|
||||
kind: "FragmentSpread",
|
||||
name: { kind: "Name", value: "UserEmail_email" },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "FragmentDefinition",
|
||||
name: { kind: "Name", value: "UserEmail_email" },
|
||||
typeCondition: {
|
||||
kind: "NamedType",
|
||||
name: { kind: "Name", value: "UserEmail" },
|
||||
},
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "email" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "confirmedAt" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<VerifyEmailMutation, VerifyEmailMutationVariables>;
|
||||
export const ResendVerificationEmailDocument = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
{
|
||||
kind: "OperationDefinition",
|
||||
operation: "mutation",
|
||||
name: { kind: "Name", value: "ResendVerificationEmail" },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: "VariableDefinition",
|
||||
variable: { kind: "Variable", name: { kind: "Name", value: "id" } },
|
||||
type: {
|
||||
kind: "NonNullType",
|
||||
type: { kind: "NamedType", name: { kind: "Name", value: "ID" } },
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "sendVerificationEmail" },
|
||||
arguments: [
|
||||
{
|
||||
kind: "Argument",
|
||||
name: { kind: "Name", value: "input" },
|
||||
value: {
|
||||
kind: "ObjectValue",
|
||||
fields: [
|
||||
{
|
||||
kind: "ObjectField",
|
||||
name: { kind: "Name", value: "userEmailId" },
|
||||
value: {
|
||||
kind: "Variable",
|
||||
name: { kind: "Name", value: "id" },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "status" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "primaryEmail" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "id" },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "email" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{
|
||||
kind: "FragmentSpread",
|
||||
name: { kind: "Name", value: "UserEmail_email" },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "FragmentDefinition",
|
||||
name: { kind: "Name", value: "UserEmail_email" },
|
||||
typeCondition: {
|
||||
kind: "NamedType",
|
||||
name: { kind: "Name", value: "UserEmail" },
|
||||
},
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "email" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "confirmedAt" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<
|
||||
ResendVerificationEmailMutation,
|
||||
ResendVerificationEmailMutationVariables
|
||||
>;
|
||||
export const RemoveEmailDocument = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
@@ -3189,7 +2989,6 @@ export const UserEmailListQueryDocument = {
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "email" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "confirmedAt" } },
|
||||
],
|
||||
},
|
||||
@@ -3323,6 +3122,242 @@ export const UserGreetingDocument = {
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<UserGreetingQuery, UserGreetingQueryVariables>;
|
||||
export const VerifyEmailDocument = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
{
|
||||
kind: "OperationDefinition",
|
||||
operation: "mutation",
|
||||
name: { kind: "Name", value: "VerifyEmail" },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: "VariableDefinition",
|
||||
variable: { kind: "Variable", name: { kind: "Name", value: "id" } },
|
||||
type: {
|
||||
kind: "NonNullType",
|
||||
type: { kind: "NamedType", name: { kind: "Name", value: "ID" } },
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "VariableDefinition",
|
||||
variable: { kind: "Variable", name: { kind: "Name", value: "code" } },
|
||||
type: {
|
||||
kind: "NonNullType",
|
||||
type: {
|
||||
kind: "NamedType",
|
||||
name: { kind: "Name", value: "String" },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "verifyEmail" },
|
||||
arguments: [
|
||||
{
|
||||
kind: "Argument",
|
||||
name: { kind: "Name", value: "input" },
|
||||
value: {
|
||||
kind: "ObjectValue",
|
||||
fields: [
|
||||
{
|
||||
kind: "ObjectField",
|
||||
name: { kind: "Name", value: "userEmailId" },
|
||||
value: {
|
||||
kind: "Variable",
|
||||
name: { kind: "Name", value: "id" },
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "ObjectField",
|
||||
name: { kind: "Name", value: "code" },
|
||||
value: {
|
||||
kind: "Variable",
|
||||
name: { kind: "Name", value: "code" },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "status" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "primaryEmail" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "id" },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "email" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{
|
||||
kind: "FragmentSpread",
|
||||
name: { kind: "Name", value: "UserEmail_email" },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "FragmentDefinition",
|
||||
name: { kind: "Name", value: "UserEmail_email" },
|
||||
typeCondition: {
|
||||
kind: "NamedType",
|
||||
name: { kind: "Name", value: "UserEmail" },
|
||||
},
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "email" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "confirmedAt" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<VerifyEmailMutation, VerifyEmailMutationVariables>;
|
||||
export const ResendVerificationEmailDocument = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
{
|
||||
kind: "OperationDefinition",
|
||||
operation: "mutation",
|
||||
name: { kind: "Name", value: "ResendVerificationEmail" },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: "VariableDefinition",
|
||||
variable: { kind: "Variable", name: { kind: "Name", value: "id" } },
|
||||
type: {
|
||||
kind: "NonNullType",
|
||||
type: { kind: "NamedType", name: { kind: "Name", value: "ID" } },
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "sendVerificationEmail" },
|
||||
arguments: [
|
||||
{
|
||||
kind: "Argument",
|
||||
name: { kind: "Name", value: "input" },
|
||||
value: {
|
||||
kind: "ObjectValue",
|
||||
fields: [
|
||||
{
|
||||
kind: "ObjectField",
|
||||
name: { kind: "Name", value: "userEmailId" },
|
||||
value: {
|
||||
kind: "Variable",
|
||||
name: { kind: "Name", value: "id" },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "status" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "primaryEmail" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "id" },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "email" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{
|
||||
kind: "FragmentSpread",
|
||||
name: { kind: "Name", value: "UserEmail_email" },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "FragmentDefinition",
|
||||
name: { kind: "Name", value: "UserEmail_email" },
|
||||
typeCondition: {
|
||||
kind: "NamedType",
|
||||
name: { kind: "Name", value: "UserEmail" },
|
||||
},
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "email" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "confirmedAt" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<
|
||||
ResendVerificationEmailMutation,
|
||||
ResendVerificationEmailMutationVariables
|
||||
>;
|
||||
export const BrowserSessionQueryDocument = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
@@ -3457,3 +3492,69 @@ export const OAuth2ClientQueryDocument = {
|
||||
OAuth2ClientQueryQuery,
|
||||
OAuth2ClientQueryQueryVariables
|
||||
>;
|
||||
export const VerifyEmailQueryDocument = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
{
|
||||
kind: "OperationDefinition",
|
||||
operation: "query",
|
||||
name: { kind: "Name", value: "VerifyEmailQuery" },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: "VariableDefinition",
|
||||
variable: { kind: "Variable", name: { kind: "Name", value: "id" } },
|
||||
type: {
|
||||
kind: "NonNullType",
|
||||
type: { kind: "NamedType", name: { kind: "Name", value: "ID" } },
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "userEmail" },
|
||||
arguments: [
|
||||
{
|
||||
kind: "Argument",
|
||||
name: { kind: "Name", value: "id" },
|
||||
value: {
|
||||
kind: "Variable",
|
||||
name: { kind: "Name", value: "id" },
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{
|
||||
kind: "FragmentSpread",
|
||||
name: { kind: "Name", value: "UserEmail_verifyEmail" },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "FragmentDefinition",
|
||||
name: { kind: "Name", value: "UserEmail_verifyEmail" },
|
||||
typeCondition: {
|
||||
kind: "NamedType",
|
||||
name: { kind: "Name", value: "UserEmail" },
|
||||
},
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "email" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<
|
||||
VerifyEmailQueryQuery,
|
||||
VerifyEmailQueryQueryVariables
|
||||
>;
|
||||
|
||||
57
frontend/src/pages/VerifyEmail.tsx
Normal file
57
frontend/src/pages/VerifyEmail.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { useAtomValue } from "jotai";
|
||||
import { atomFamily } from "jotai/utils";
|
||||
import { atomWithQuery } from "jotai-urql";
|
||||
|
||||
import { mapQueryAtom } from "../atoms";
|
||||
import GraphQLError from "../components/GraphQLError";
|
||||
import VerifyEmailComponent from "../components/VerifyEmail";
|
||||
import { graphql } from "../gql";
|
||||
import { isErr, unwrapErr, unwrapOk } from "../result";
|
||||
|
||||
const QUERY = graphql(/* GraphQL */ `
|
||||
query VerifyEmailQuery($id: ID!) {
|
||||
userEmail(id: $id) {
|
||||
...UserEmail_verifyEmail
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const verifyEmailFamily = atomFamily((id: string) => {
|
||||
const verifyEmailQueryAtom = atomWithQuery({
|
||||
query: QUERY,
|
||||
getVariables: () => ({ id }),
|
||||
});
|
||||
|
||||
const verifyEmailAtom = mapQueryAtom(
|
||||
verifyEmailQueryAtom,
|
||||
(data) => data?.userEmail,
|
||||
);
|
||||
|
||||
return verifyEmailAtom;
|
||||
});
|
||||
|
||||
const VerifyEmail: React.FC<{ id: string }> = ({ id }) => {
|
||||
const result = useAtomValue(verifyEmailFamily(id));
|
||||
if (isErr(result)) return <GraphQLError error={unwrapErr(result)} />;
|
||||
|
||||
const email = unwrapOk(result);
|
||||
if (email == null) return <>Unknown email</>;
|
||||
|
||||
return <VerifyEmailComponent email={email} />;
|
||||
};
|
||||
|
||||
export default VerifyEmail;
|
||||
Reference in New Issue
Block a user