Files
matrix-authentication-service/frontend/src/components/UserProfile/UserName.tsx
T

162 lines
4.5 KiB
TypeScript

// 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, ChangeEventHandler } 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 [setDisplayNameResult, setDisplayName] = useAtom(
setDisplayNameFamily(userId),
);
const [inProgress, setInProgress] = useState(false);
const user = result.data?.user;
const displayName = user?.matrix.displayName || "";
const [fieldValue, setFieldValue] = useState(displayName);
const userGreeting = useSetAtom(userGreetingFamily(userId));
useEffect(() => {
setFieldValue(displayName);
}, [displayName]);
const hasChanges = fieldValue !== displayName;
const onChange: ChangeEventHandler<HTMLInputElement> = (event): void => {
setFieldValue(event.target.value);
};
const onSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault();
let newDisplayName = fieldValue || 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
setFieldValue(displayName);
}
setInProgress(false);
});
};
const errorMessage = getErrorMessage(setDisplayNameResult);
return (
<Root onSubmit={onSubmit} className={styles.form}>
<Field name="displayname">
<Label>Display Name</Label>
<Control
value={fieldValue}
onChange={onChange}
inputMode="text"
max={250}
/>
</Field>
{!inProgress && errorMessage && (
<Alert type="critical" title="Error">
{errorMessage}
</Alert>
)}
<Button
className={styles.saveButton}
disabled={inProgress || !hasChanges}
kind="primary"
size="sm"
type="submit"
>
{!!inProgress && <LoadingSpinner inline />}Save
</Button>
</Root>
);
};
export default UserName;