You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-11-23 11:02:35 +03:00
Allow user to view and change display name in My Account UI (#1628)
Co-authored-by: Quentin Gliech <quenting@element.io>
This commit is contained in:
@@ -19,6 +19,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
--size: var(--cpd-space-16x);
|
||||
}
|
||||
|
||||
.inline {
|
||||
display: inline;
|
||||
margin-right: var(--cpd-space-4x);
|
||||
|
||||
.loading-spinner-inner {
|
||||
height: var(--cpd-space-4x);
|
||||
width: var(--cpd-space-4x);
|
||||
}
|
||||
}
|
||||
|
||||
.loading-spinner-inner {
|
||||
height: var(--cpd-space-16x);
|
||||
width: var(--cpd-space-16x);
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
|
||||
import styles from "./LoadingSpinner.module.css";
|
||||
|
||||
const LoadingSpinner: React.FC = () => (
|
||||
<div role="status">
|
||||
const LoadingSpinner: React.FC<{ inline?: boolean }> = ({ inline }) => (
|
||||
<div role="status" className={inline ? styles.inline : undefined}>
|
||||
<svg
|
||||
className={styles.loadingSpinnerInner}
|
||||
viewBox="0 0 100 101"
|
||||
|
||||
@@ -32,7 +32,7 @@ const QUERY = graphql(/* GraphQL */ `
|
||||
}
|
||||
`);
|
||||
|
||||
const userGreetingFamily = atomFamily((userId: string) => {
|
||||
export const userGreetingFamily = atomFamily((userId: string) => {
|
||||
const userGreeting = atomWithQuery({
|
||||
query: QUERY,
|
||||
getVariables: () => ({ userId }),
|
||||
|
||||
@@ -24,7 +24,7 @@ import { useAtom } from "jotai";
|
||||
import { atomWithMutation } from "jotai-urql";
|
||||
import { useRef, useTransition } from "react";
|
||||
|
||||
import { graphql } from "../gql";
|
||||
import { graphql } from "../../gql";
|
||||
|
||||
const ADD_EMAIL_MUTATION = graphql(/* GraphQL */ `
|
||||
mutation AddEmail($userId: ID!, $email: String!) {
|
||||
@@ -12,26 +12,26 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Alert } from "@vector-im/compound-web";
|
||||
import { Alert, H3 } from "@vector-im/compound-web";
|
||||
import { atom, useAtom, useAtomValue, useSetAtom } from "jotai";
|
||||
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 { routeAtom } from "../../Router";
|
||||
import { graphql } from "../../gql";
|
||||
import { PageInfo } from "../../gql/graphql";
|
||||
import {
|
||||
atomForCurrentPagination,
|
||||
atomWithPagination,
|
||||
FIRST_PAGE,
|
||||
Pagination,
|
||||
} from "../pagination";
|
||||
} from "../../pagination";
|
||||
import BlockList from "../BlockList";
|
||||
import PaginationControls from "../PaginationControls";
|
||||
import UserEmail from "../UserEmail";
|
||||
|
||||
import AddEmailForm from "./AddEmailForm";
|
||||
import BlockList from "./BlockList";
|
||||
import PaginationControls from "./PaginationControls";
|
||||
import UserEmail from "./UserEmail";
|
||||
|
||||
const QUERY = graphql(/* GraphQL */ `
|
||||
query UserEmailListQuery(
|
||||
@@ -155,10 +155,11 @@ const UserEmailList: React.FC<{
|
||||
setRoute({ type: "verify-email", id });
|
||||
};
|
||||
|
||||
const showNoPrimaryEmailAlert = !!result && !primaryEmailId;
|
||||
const showNoPrimaryEmailAlert = !!result?.data && !primaryEmailId;
|
||||
|
||||
return (
|
||||
<BlockList>
|
||||
<H3>Emails</H3>
|
||||
<PaginationControls
|
||||
count={result.data?.user?.emails?.totalCount ?? 0}
|
||||
onPrev={prevPage ? (): void => paginate(prevPage) : null}
|
||||
25
frontend/src/components/UserProfile/UserName.module.css
Normal file
25
frontend/src/components/UserProfile/UserName.module.css
Normal file
@@ -0,0 +1,25 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: var(--cpd-space-2x);
|
||||
}
|
||||
|
||||
.save-button {
|
||||
align-self: flex-start;
|
||||
}
|
||||
155
frontend/src/components/UserProfile/UserName.tsx
Normal file
155
frontend/src/components/UserProfile/UserName.tsx
Normal file
@@ -0,0 +1,155 @@
|
||||
// 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 {
|
||||
Alert,
|
||||
Control,
|
||||
Field,
|
||||
Label,
|
||||
Root,
|
||||
Button,
|
||||
} from "@vector-im/compound-web";
|
||||
import { useAtomValue, useAtom, useSetAtom, atom } from "jotai";
|
||||
import { atomFamily } from "jotai/utils";
|
||||
import { atomWithMutation } from "jotai-urql";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
|
||||
import { graphql } from "../../gql";
|
||||
import LoadingSpinner from "../LoadingSpinner/LoadingSpinner";
|
||||
import { userGreetingFamily } from "../UserGreeting";
|
||||
|
||||
import styles from "./UserName.module.css";
|
||||
|
||||
const SET_DISPLAYNAME_MUTATION = graphql(/* GraphQL */ `
|
||||
mutation SetDisplayName($userId: ID!, $displayName: String) {
|
||||
setDisplayName(input: { userId: $userId, displayName: $displayName }) {
|
||||
status
|
||||
user {
|
||||
id
|
||||
matrix {
|
||||
displayName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const setDisplayNameFamily = atomFamily((userId: string) => {
|
||||
const setDisplayName = atomWithMutation(SET_DISPLAYNAME_MUTATION);
|
||||
|
||||
// A proxy atom which pre-sets the id variable in the mutation
|
||||
const setDisplayNameAtom = atom(
|
||||
(get) => get(setDisplayName),
|
||||
(get, set, displayName: string | null) =>
|
||||
set(setDisplayName, { userId, displayName }),
|
||||
);
|
||||
|
||||
return setDisplayNameAtom;
|
||||
});
|
||||
|
||||
const getErrorMessage = (result: {
|
||||
error?: unknown;
|
||||
data?: { setDisplayName: { status: string } };
|
||||
}): string | undefined => {
|
||||
if (result.error) {
|
||||
return "Failed to save display name. Please try again.";
|
||||
}
|
||||
if (result.data?.setDisplayName.status === "INVALID") {
|
||||
return "Failed to save invalid display name.";
|
||||
}
|
||||
};
|
||||
|
||||
const UserName: React.FC<{ userId: string }> = ({ userId }) => {
|
||||
const result = useAtomValue(userGreetingFamily(userId));
|
||||
const fieldRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const [setDisplayNameResult, setDisplayName] = useAtom(
|
||||
setDisplayNameFamily(userId),
|
||||
);
|
||||
const [inProgress, setInProgress] = useState(false);
|
||||
|
||||
const user = result.data?.user;
|
||||
const displayName = user?.matrix.displayName || "";
|
||||
|
||||
const userGreeting = useSetAtom(userGreetingFamily(userId));
|
||||
|
||||
useEffect(() => {
|
||||
if (fieldRef.current) {
|
||||
fieldRef.current.value = displayName;
|
||||
}
|
||||
}, [displayName]);
|
||||
|
||||
const onSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
|
||||
event.preventDefault();
|
||||
|
||||
const formData = new FormData(event.currentTarget);
|
||||
let newDisplayName = (formData.get("displayname") as string) || null;
|
||||
|
||||
// set null to remove an existing username
|
||||
if (newDisplayName === "") {
|
||||
newDisplayName = null;
|
||||
}
|
||||
|
||||
// do nothing if no change
|
||||
if ((!newDisplayName && !displayName) || newDisplayName === displayName) {
|
||||
return;
|
||||
}
|
||||
|
||||
setInProgress(true);
|
||||
setDisplayName(newDisplayName).then((result) => {
|
||||
if (!result.data) {
|
||||
console.error("Failed to set display name", result.error);
|
||||
} else if (result.data.setDisplayName.status === "SET") {
|
||||
// refresh the user greeting after changing the display name
|
||||
userGreeting({
|
||||
requestPolicy: "network-only",
|
||||
});
|
||||
} else if (result.data.setDisplayName.status === "INVALID") {
|
||||
// reset to current saved display name
|
||||
if (fieldRef.current) {
|
||||
fieldRef.current.value = displayName;
|
||||
}
|
||||
}
|
||||
setInProgress(false);
|
||||
});
|
||||
};
|
||||
|
||||
const errorMessage = getErrorMessage(setDisplayNameResult);
|
||||
|
||||
return (
|
||||
<Root onSubmit={onSubmit} className={styles.form}>
|
||||
<Field name="displayname">
|
||||
<Label>Display Name</Label>
|
||||
<Control ref={fieldRef} inputMode="text" max={250} />
|
||||
</Field>
|
||||
{!inProgress && errorMessage && (
|
||||
<Alert type="critical" title="Error">
|
||||
{errorMessage}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Button
|
||||
className={styles.saveButton}
|
||||
disabled={inProgress}
|
||||
kind="primary"
|
||||
size="sm"
|
||||
type="submit"
|
||||
>
|
||||
{!!inProgress && <LoadingSpinner inline />}Save
|
||||
</Button>
|
||||
</Root>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserName;
|
||||
29
frontend/src/components/UserProfile/UserProfile.tsx
Normal file
29
frontend/src/components/UserProfile/UserProfile.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
// 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 BlockList from "../BlockList/BlockList";
|
||||
|
||||
import UserEmailList from "./UserEmailList";
|
||||
import UserName from "./UserName";
|
||||
|
||||
const UserProfile: React.FC<{ userId: string }> = ({ userId }) => {
|
||||
return (
|
||||
<BlockList>
|
||||
<UserName userId={userId} />
|
||||
<UserEmailList userId={userId} />
|
||||
</BlockList>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserProfile;
|
||||
15
frontend/src/components/UserProfile/index.ts
Normal file
15
frontend/src/components/UserProfile/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 "./UserProfile";
|
||||
@@ -17,8 +17,6 @@ const documents = {
|
||||
types.CurrentViewerQueryDocument,
|
||||
"\n query CurrentViewerSessionQuery {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n }\n\n ... on Anonymous {\n id\n }\n }\n }\n":
|
||||
types.CurrentViewerSessionQueryDocument,
|
||||
"\n mutation AddEmail($userId: ID!, $email: String!) {\n addEmail(input: { userId: $userId, email: $email }) {\n status\n email {\n id\n ...UserEmail_email\n }\n }\n }\n":
|
||||
types.AddEmailDocument,
|
||||
"\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n finishedAt\n lastAuthentication {\n id\n createdAt\n }\n }\n":
|
||||
types.BrowserSession_SessionFragmentDoc,
|
||||
"\n mutation EndBrowserSession($id: ID!) {\n endBrowserSession(input: { browserSessionId: $id }) {\n status\n browserSession {\n id\n ...BrowserSession_session\n }\n }\n }\n":
|
||||
@@ -45,14 +43,18 @@ const documents = {
|
||||
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":
|
||||
types.SetPrimaryEmailDocument,
|
||||
"\n query UserEmailListQuery(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n\n emails(first: $first, after: $after, last: $last, before: $before) {\n edges {\n cursor\n node {\n id\n ...UserEmail_email\n }\n }\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n":
|
||||
types.UserEmailListQueryDocument,
|
||||
"\n query UserPrimaryEmail($userId: ID!) {\n user(id: $userId) {\n id\n primaryEmail {\n id\n }\n }\n }\n":
|
||||
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 UserHome_user on User {\n id\n\n primaryEmail {\n id\n ...UserEmail_email\n }\n\n confirmedEmails: emails(first: 0, state: CONFIRMED) {\n totalCount\n }\n\n unverifiedEmails: emails(first: 0, state: PENDING) {\n totalCount\n }\n\n browserSessions(first: 0, state: ACTIVE) {\n totalCount\n }\n\n oauth2Sessions(first: 0, state: ACTIVE) {\n totalCount\n }\n\n compatSessions(first: 0, state: ACTIVE) {\n totalCount\n }\n }\n":
|
||||
types.UserHome_UserFragmentDoc,
|
||||
"\n mutation AddEmail($userId: ID!, $email: String!) {\n addEmail(input: { userId: $userId, email: $email }) {\n status\n email {\n id\n ...UserEmail_email\n }\n }\n }\n":
|
||||
types.AddEmailDocument,
|
||||
"\n query UserEmailListQuery(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n\n emails(first: $first, after: $after, last: $last, before: $before) {\n edges {\n cursor\n node {\n id\n ...UserEmail_email\n }\n }\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n":
|
||||
types.UserEmailListQueryDocument,
|
||||
"\n query UserPrimaryEmail($userId: ID!) {\n user(id: $userId) {\n id\n primaryEmail {\n id\n }\n }\n }\n":
|
||||
types.UserPrimaryEmailDocument,
|
||||
"\n mutation SetDisplayName($userId: ID!, $displayName: String) {\n setDisplayName(input: { userId: $userId, displayName: $displayName }) {\n status\n user {\n id\n matrix {\n displayName\n }\n }\n }\n }\n":
|
||||
types.SetDisplayNameDocument,
|
||||
"\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":
|
||||
@@ -95,12 +97,6 @@ export function graphql(
|
||||
export function graphql(
|
||||
source: "\n query CurrentViewerSessionQuery {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n }\n\n ... on Anonymous {\n id\n }\n }\n }\n",
|
||||
): (typeof documents)["\n query CurrentViewerSessionQuery {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n }\n\n ... on Anonymous {\n id\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 AddEmail($userId: ID!, $email: String!) {\n addEmail(input: { userId: $userId, email: $email }) {\n status\n email {\n id\n ...UserEmail_email\n }\n }\n }\n",
|
||||
): (typeof documents)["\n mutation AddEmail($userId: ID!, $email: String!) {\n addEmail(input: { userId: $userId, email: $email }) {\n status\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.
|
||||
*/
|
||||
@@ -179,6 +175,24 @@ export function graphql(
|
||||
export function graphql(
|
||||
source: "\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",
|
||||
): (typeof documents)["\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"];
|
||||
/**
|
||||
* 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 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 UserHome_user on User {\n id\n\n primaryEmail {\n id\n ...UserEmail_email\n }\n\n confirmedEmails: emails(first: 0, state: CONFIRMED) {\n totalCount\n }\n\n unverifiedEmails: emails(first: 0, state: PENDING) {\n totalCount\n }\n\n browserSessions(first: 0, state: ACTIVE) {\n totalCount\n }\n\n oauth2Sessions(first: 0, state: ACTIVE) {\n totalCount\n }\n\n compatSessions(first: 0, state: ACTIVE) {\n totalCount\n }\n }\n",
|
||||
): (typeof documents)["\n fragment UserHome_user on User {\n id\n\n primaryEmail {\n id\n ...UserEmail_email\n }\n\n confirmedEmails: emails(first: 0, state: CONFIRMED) {\n totalCount\n }\n\n unverifiedEmails: emails(first: 0, state: PENDING) {\n totalCount\n }\n\n browserSessions(first: 0, state: ACTIVE) {\n totalCount\n }\n\n oauth2Sessions(first: 0, state: ACTIVE) {\n totalCount\n }\n\n compatSessions(first: 0, state: ACTIVE) {\n totalCount\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 AddEmail($userId: ID!, $email: String!) {\n addEmail(input: { userId: $userId, email: $email }) {\n status\n email {\n id\n ...UserEmail_email\n }\n }\n }\n",
|
||||
): (typeof documents)["\n mutation AddEmail($userId: ID!, $email: String!) {\n addEmail(input: { userId: $userId, email: $email }) {\n status\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.
|
||||
*/
|
||||
@@ -195,14 +209,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 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 UserHome_user on User {\n id\n\n primaryEmail {\n id\n ...UserEmail_email\n }\n\n confirmedEmails: emails(first: 0, state: CONFIRMED) {\n totalCount\n }\n\n unverifiedEmails: emails(first: 0, state: PENDING) {\n totalCount\n }\n\n browserSessions(first: 0, state: ACTIVE) {\n totalCount\n }\n\n oauth2Sessions(first: 0, state: ACTIVE) {\n totalCount\n }\n\n compatSessions(first: 0, state: ACTIVE) {\n totalCount\n }\n }\n",
|
||||
): (typeof documents)["\n fragment UserHome_user on User {\n id\n\n primaryEmail {\n id\n ...UserEmail_email\n }\n\n confirmedEmails: emails(first: 0, state: CONFIRMED) {\n totalCount\n }\n\n unverifiedEmails: emails(first: 0, state: PENDING) {\n totalCount\n }\n\n browserSessions(first: 0, state: ACTIVE) {\n totalCount\n }\n\n oauth2Sessions(first: 0, state: ACTIVE) {\n totalCount\n }\n\n compatSessions(first: 0, state: ACTIVE) {\n totalCount\n }\n }\n"];
|
||||
source: "\n mutation SetDisplayName($userId: ID!, $displayName: String) {\n setDisplayName(input: { userId: $userId, displayName: $displayName }) {\n status\n user {\n id\n matrix {\n displayName\n }\n }\n }\n }\n",
|
||||
): (typeof documents)["\n mutation SetDisplayName($userId: ID!, $displayName: String) {\n setDisplayName(input: { userId: $userId, displayName: $displayName }) {\n status\n user {\n id\n matrix {\n displayName\n }\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
|
||||
@@ -916,26 +916,6 @@ export type CurrentViewerSessionQueryQuery = {
|
||||
| { __typename: "Oauth2Session" };
|
||||
};
|
||||
|
||||
export type AddEmailMutationVariables = Exact<{
|
||||
userId: Scalars["ID"]["input"];
|
||||
email: Scalars["String"]["input"];
|
||||
}>;
|
||||
|
||||
export type AddEmailMutation = {
|
||||
__typename?: "Mutation";
|
||||
addEmail: {
|
||||
__typename?: "AddEmailPayload";
|
||||
status: AddEmailStatus;
|
||||
email?:
|
||||
| ({ __typename?: "UserEmail"; id: string } & {
|
||||
" $fragmentRefs"?: {
|
||||
UserEmail_EmailFragment: UserEmail_EmailFragment;
|
||||
};
|
||||
})
|
||||
| null;
|
||||
};
|
||||
};
|
||||
|
||||
export type BrowserSession_SessionFragment = {
|
||||
__typename?: "BrowserSession";
|
||||
id: string;
|
||||
@@ -1186,6 +1166,68 @@ export type SetPrimaryEmailMutation = {
|
||||
};
|
||||
};
|
||||
|
||||
export type UserGreetingQueryVariables = Exact<{
|
||||
userId: Scalars["ID"]["input"];
|
||||
}>;
|
||||
|
||||
export type UserGreetingQuery = {
|
||||
__typename?: "Query";
|
||||
user?: {
|
||||
__typename?: "User";
|
||||
id: string;
|
||||
username: string;
|
||||
matrix: {
|
||||
__typename?: "MatrixUser";
|
||||
mxid: string;
|
||||
displayName?: string | null;
|
||||
};
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type UserHome_UserFragment = {
|
||||
__typename?: "User";
|
||||
id: string;
|
||||
primaryEmail?:
|
||||
| ({ __typename?: "UserEmail"; id: string } & {
|
||||
" $fragmentRefs"?: { UserEmail_EmailFragment: UserEmail_EmailFragment };
|
||||
})
|
||||
| null;
|
||||
confirmedEmails: { __typename?: "UserEmailConnection"; totalCount: number };
|
||||
unverifiedEmails: { __typename?: "UserEmailConnection"; totalCount: number };
|
||||
browserSessions: {
|
||||
__typename?: "BrowserSessionConnection";
|
||||
totalCount: number;
|
||||
};
|
||||
oauth2Sessions: {
|
||||
__typename?: "Oauth2SessionConnection";
|
||||
totalCount: number;
|
||||
};
|
||||
compatSessions: {
|
||||
__typename?: "CompatSessionConnection";
|
||||
totalCount: number;
|
||||
};
|
||||
} & { " $fragmentName"?: "UserHome_UserFragment" };
|
||||
|
||||
export type AddEmailMutationVariables = Exact<{
|
||||
userId: Scalars["ID"]["input"];
|
||||
email: Scalars["String"]["input"];
|
||||
}>;
|
||||
|
||||
export type AddEmailMutation = {
|
||||
__typename?: "Mutation";
|
||||
addEmail: {
|
||||
__typename?: "AddEmailPayload";
|
||||
status: AddEmailStatus;
|
||||
email?:
|
||||
| ({ __typename?: "UserEmail"; id: string } & {
|
||||
" $fragmentRefs"?: {
|
||||
UserEmail_EmailFragment: UserEmail_EmailFragment;
|
||||
};
|
||||
})
|
||||
| null;
|
||||
};
|
||||
};
|
||||
|
||||
export type UserEmailListQueryQueryVariables = Exact<{
|
||||
userId: Scalars["ID"]["input"];
|
||||
first?: InputMaybe<Scalars["Int"]["input"]>;
|
||||
@@ -1235,47 +1277,23 @@ export type UserPrimaryEmailQuery = {
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type UserGreetingQueryVariables = Exact<{
|
||||
export type SetDisplayNameMutationVariables = Exact<{
|
||||
userId: Scalars["ID"]["input"];
|
||||
displayName?: InputMaybe<Scalars["String"]["input"]>;
|
||||
}>;
|
||||
|
||||
export type UserGreetingQuery = {
|
||||
__typename?: "Query";
|
||||
export type SetDisplayNameMutation = {
|
||||
__typename?: "Mutation";
|
||||
setDisplayName: {
|
||||
__typename?: "SetDisplayNamePayload";
|
||||
status: SetDisplayNameStatus;
|
||||
user?: {
|
||||
__typename?: "User";
|
||||
id: string;
|
||||
username: string;
|
||||
matrix: {
|
||||
__typename?: "MatrixUser";
|
||||
mxid: string;
|
||||
displayName?: string | null;
|
||||
};
|
||||
matrix: { __typename?: "MatrixUser"; displayName?: string | null };
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type UserHome_UserFragment = {
|
||||
__typename?: "User";
|
||||
id: string;
|
||||
primaryEmail?:
|
||||
| ({ __typename?: "UserEmail"; id: string } & {
|
||||
" $fragmentRefs"?: { UserEmail_EmailFragment: UserEmail_EmailFragment };
|
||||
})
|
||||
| null;
|
||||
confirmedEmails: { __typename?: "UserEmailConnection"; totalCount: number };
|
||||
unverifiedEmails: { __typename?: "UserEmailConnection"; totalCount: number };
|
||||
browserSessions: {
|
||||
__typename?: "BrowserSessionConnection";
|
||||
totalCount: number;
|
||||
};
|
||||
oauth2Sessions: {
|
||||
__typename?: "Oauth2SessionConnection";
|
||||
totalCount: number;
|
||||
};
|
||||
compatSessions: {
|
||||
__typename?: "CompatSessionConnection";
|
||||
totalCount: number;
|
||||
};
|
||||
} & { " $fragmentName"?: "UserHome_UserFragment" };
|
||||
|
||||
export type UserEmail_VerifyEmailFragment = {
|
||||
__typename?: "UserEmail";
|
||||
@@ -1839,115 +1857,6 @@ export const CurrentViewerSessionQueryDocument = {
|
||||
CurrentViewerSessionQueryQuery,
|
||||
CurrentViewerSessionQueryQueryVariables
|
||||
>;
|
||||
export const AddEmailDocument = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
{
|
||||
kind: "OperationDefinition",
|
||||
operation: "mutation",
|
||||
name: { kind: "Name", value: "AddEmail" },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: "VariableDefinition",
|
||||
variable: {
|
||||
kind: "Variable",
|
||||
name: { kind: "Name", value: "userId" },
|
||||
},
|
||||
type: {
|
||||
kind: "NonNullType",
|
||||
type: { kind: "NamedType", name: { kind: "Name", value: "ID" } },
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "VariableDefinition",
|
||||
variable: {
|
||||
kind: "Variable",
|
||||
name: { kind: "Name", value: "email" },
|
||||
},
|
||||
type: {
|
||||
kind: "NonNullType",
|
||||
type: {
|
||||
kind: "NamedType",
|
||||
name: { kind: "Name", value: "String" },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "addEmail" },
|
||||
arguments: [
|
||||
{
|
||||
kind: "Argument",
|
||||
name: { kind: "Name", value: "input" },
|
||||
value: {
|
||||
kind: "ObjectValue",
|
||||
fields: [
|
||||
{
|
||||
kind: "ObjectField",
|
||||
name: { kind: "Name", value: "userId" },
|
||||
value: {
|
||||
kind: "Variable",
|
||||
name: { kind: "Name", value: "userId" },
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "ObjectField",
|
||||
name: { kind: "Name", value: "email" },
|
||||
value: {
|
||||
kind: "Variable",
|
||||
name: { kind: "Name", value: "email" },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "status" } },
|
||||
{
|
||||
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<AddEmailMutation, AddEmailMutationVariables>;
|
||||
export const EndBrowserSessionDocument = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
@@ -3073,6 +2982,178 @@ export const SetPrimaryEmailDocument = {
|
||||
SetPrimaryEmailMutation,
|
||||
SetPrimaryEmailMutationVariables
|
||||
>;
|
||||
export const UserGreetingDocument = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
{
|
||||
kind: "OperationDefinition",
|
||||
operation: "query",
|
||||
name: { kind: "Name", value: "UserGreeting" },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: "VariableDefinition",
|
||||
variable: {
|
||||
kind: "Variable",
|
||||
name: { kind: "Name", value: "userId" },
|
||||
},
|
||||
type: {
|
||||
kind: "NonNullType",
|
||||
type: { kind: "NamedType", name: { kind: "Name", value: "ID" } },
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user" },
|
||||
arguments: [
|
||||
{
|
||||
kind: "Argument",
|
||||
name: { kind: "Name", value: "id" },
|
||||
value: {
|
||||
kind: "Variable",
|
||||
name: { kind: "Name", value: "userId" },
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "username" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "matrix" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "mxid" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "displayName" },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<UserGreetingQuery, UserGreetingQueryVariables>;
|
||||
export const AddEmailDocument = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
{
|
||||
kind: "OperationDefinition",
|
||||
operation: "mutation",
|
||||
name: { kind: "Name", value: "AddEmail" },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: "VariableDefinition",
|
||||
variable: {
|
||||
kind: "Variable",
|
||||
name: { kind: "Name", value: "userId" },
|
||||
},
|
||||
type: {
|
||||
kind: "NonNullType",
|
||||
type: { kind: "NamedType", name: { kind: "Name", value: "ID" } },
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "VariableDefinition",
|
||||
variable: {
|
||||
kind: "Variable",
|
||||
name: { kind: "Name", value: "email" },
|
||||
},
|
||||
type: {
|
||||
kind: "NonNullType",
|
||||
type: {
|
||||
kind: "NamedType",
|
||||
name: { kind: "Name", value: "String" },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "addEmail" },
|
||||
arguments: [
|
||||
{
|
||||
kind: "Argument",
|
||||
name: { kind: "Name", value: "input" },
|
||||
value: {
|
||||
kind: "ObjectValue",
|
||||
fields: [
|
||||
{
|
||||
kind: "ObjectField",
|
||||
name: { kind: "Name", value: "userId" },
|
||||
value: {
|
||||
kind: "Variable",
|
||||
name: { kind: "Name", value: "userId" },
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "ObjectField",
|
||||
name: { kind: "Name", value: "email" },
|
||||
value: {
|
||||
kind: "Variable",
|
||||
name: { kind: "Name", value: "email" },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "status" } },
|
||||
{
|
||||
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<AddEmailMutation, AddEmailMutationVariables>;
|
||||
export const UserEmailListQueryDocument = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
@@ -3335,13 +3416,13 @@ export const UserPrimaryEmailDocument = {
|
||||
UserPrimaryEmailQuery,
|
||||
UserPrimaryEmailQueryVariables
|
||||
>;
|
||||
export const UserGreetingDocument = {
|
||||
export const SetDisplayNameDocument = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
{
|
||||
kind: "OperationDefinition",
|
||||
operation: "query",
|
||||
name: { kind: "Name", value: "UserGreeting" },
|
||||
operation: "mutation",
|
||||
name: { kind: "Name", value: "SetDisplayName" },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: "VariableDefinition",
|
||||
@@ -3354,35 +3435,65 @@ export const UserGreetingDocument = {
|
||||
type: { kind: "NamedType", name: { kind: "Name", value: "ID" } },
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "VariableDefinition",
|
||||
variable: {
|
||||
kind: "Variable",
|
||||
name: { kind: "Name", value: "displayName" },
|
||||
},
|
||||
type: { kind: "NamedType", name: { kind: "Name", value: "String" } },
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user" },
|
||||
name: { kind: "Name", value: "setDisplayName" },
|
||||
arguments: [
|
||||
{
|
||||
kind: "Argument",
|
||||
name: { kind: "Name", value: "id" },
|
||||
name: { kind: "Name", value: "input" },
|
||||
value: {
|
||||
kind: "ObjectValue",
|
||||
fields: [
|
||||
{
|
||||
kind: "ObjectField",
|
||||
name: { kind: "Name", value: "userId" },
|
||||
value: {
|
||||
kind: "Variable",
|
||||
name: { kind: "Name", value: "userId" },
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "ObjectField",
|
||||
name: { kind: "Name", value: "displayName" },
|
||||
value: {
|
||||
kind: "Variable",
|
||||
name: { kind: "Name", value: "displayName" },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
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: "username" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "matrix" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "mxid" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "displayName" },
|
||||
@@ -3397,7 +3508,13 @@ export const UserGreetingDocument = {
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<UserGreetingQuery, UserGreetingQueryVariables>;
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<
|
||||
SetDisplayNameMutation,
|
||||
SetDisplayNameMutationVariables
|
||||
>;
|
||||
export const VerifyEmailDocument = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
|
||||
@@ -17,7 +17,7 @@ import { useAtomValue } from "jotai";
|
||||
import { currentUserIdAtom } from "../atoms";
|
||||
import GraphQLError from "../components/GraphQLError";
|
||||
import NotLoggedIn from "../components/NotLoggedIn";
|
||||
import UserEmailList from "../components/UserEmailList";
|
||||
import UserProfile from "../components/UserProfile";
|
||||
import { isErr, unwrapErr, unwrapOk } from "../result";
|
||||
|
||||
const Profile: React.FC = () => {
|
||||
@@ -27,7 +27,7 @@ const Profile: React.FC = () => {
|
||||
const userId = unwrapOk(result);
|
||||
if (userId === null) return <NotLoggedIn />;
|
||||
|
||||
return <UserEmailList userId={userId} />;
|
||||
return <UserProfile userId={userId} />;
|
||||
};
|
||||
|
||||
export default Profile;
|
||||
|
||||
Reference in New Issue
Block a user