Path: blob/main/components/dashboard/src/user-settings/Account.tsx
2500 views
/**1* Copyright (c) 2021 Gitpod GmbH. All rights reserved.2* Licensed under the GNU Affero General Public License (AGPL).3* See License.AGPL.txt in the project root for license information.4*/56import { useCallback, useContext, useState } from "react";7import { getGitpodService, gitpodHostUrl } from "../service/service";8import { UserContext } from "../user-context";9import ConfirmationModal from "../components/ConfirmationModal";10import { PageWithSettingsSubMenu } from "./PageWithSettingsSubMenu";11import { Button } from "@podkit/buttons/Button";12import { Heading2, Subheading } from "../components/typography/headings";13import Alert from "../components/Alert";14import { TextInputField } from "../components/forms/TextInputField";15import isEmail from "validator/lib/isEmail";16import { useToast } from "../components/toasts/Toasts";17import { InputWithCopy } from "../components/InputWithCopy";18import { InputField } from "../components/forms/InputField";19import { getPrimaryEmail, isOrganizationOwned } from "@gitpod/public-api-common/lib/user-utils";20import { User } from "@gitpod/public-api/lib/gitpod/v1/user_pb";21import { User as UserProtocol, ProfileDetails } from "@gitpod/gitpod-protocol";22import { useUpdateCurrentUserMutation } from "../data/current-user/update-mutation";2324type UserProfile = Pick<ProfileDetails, "emailAddress"> & Required<Pick<UserProtocol, "name" | "avatarUrl">>;25function getProfile(user: User): UserProfile {26return {27name: user.name,28avatarUrl: user.avatarUrl,29emailAddress: getPrimaryEmail(user),30};31}3233export default function Account() {34const { user, setUser } = useContext(UserContext);35const [modal, setModal] = useState(false);36const [typedEmail, setTypedEmail] = useState("");37const original = getProfile(user!);38const [profileState, setProfileState] = useState(original);39const [errorMessage, setErrorMessage] = useState("");40const canUpdateEmail = user && !isOrganizationOwned(user);41const { toast } = useToast();42const updateUser = useUpdateCurrentUserMutation();4344const saveProfileState = useCallback(async () => {45if (!user || !profileState) {46return;47}4849if (profileState.name.trim() === "") {50setErrorMessage("Name must not be empty.");51return;52}53if (canUpdateEmail) {54if (!profileState.emailAddress?.trim()) {55setErrorMessage("Email must not be empty.");56return;57}58// check valid email59if (!isEmail(profileState.emailAddress?.trim() || "")) {60setErrorMessage("Please enter a valid email.");61return;62}63} else {64profileState.emailAddress = getPrimaryEmail(user) || "";65}6667const updatedUser = await updateUser.mutateAsync({68fullName: profileState.name,69additionalData: {70profile: profileState,71},72});73setUser(updatedUser);74toast("Your profile information has been updated.");75}, [updateUser, canUpdateEmail, profileState, setUser, toast, user]);7677const deleteAccount = useCallback(async () => {78await getGitpodService().server.deleteAccount();79document.location.href = gitpodHostUrl.asApiLogout().toString();80}, []);8182const close = () => setModal(false);83return (84<div>85<ConfirmationModal86title="Delete Account"87areYouSureText="You are about to permanently delete your account."88buttonText="Delete Account"89buttonDisabled={typedEmail !== (original.emailAddress || "")}90visible={modal}91onClose={close}92onConfirm={deleteAccount}93>94<ol className="text-gray-500 text-sm list-outside list-decimal">95<li className="ml-5">96All your workspaces and related data will be deleted and cannot be restored afterwards.97</li>98<li className="ml-5">99Your subscription will be cancelled. If you obtained a Gitpod subscription through the GitHub100marketplace, you need to cancel your plan there.101</li>102</ol>103<p className="pt-4 pb-2 text-gray-600 dark:text-gray-400 text-base font-semibold">104Type your email to confirm105</p>106<input autoFocus className="w-full" type="text" onChange={(e) => setTypedEmail(e.target.value)}></input>107</ConfirmationModal>108109<PageWithSettingsSubMenu>110<Heading2>Profile</Heading2>111<form112onSubmit={(e) => {113e.preventDefault();114saveProfileState();115}}116>117<ProfileInformation118profileState={profileState}119setProfileState={(state) => {120setProfileState(state);121}}122errorMessage={errorMessage}123emailIsReadonly={!canUpdateEmail}124user={user}125>126<div className="flex flex-row mt-8">127<Button type="submit">Update Profile</Button>128</div>129</ProfileInformation>130</form>131<Heading2 className="mt-12">Delete Account</Heading2>132<Subheading className="mb-3">133This action will remove all the data associated with your account in Gitpod.134</Subheading>135<Button variant="destructive" onClick={() => setModal(true)}>136Delete Account137</Button>138</PageWithSettingsSubMenu>139</div>140);141}142143function ProfileInformation(props: {144profileState: UserProfile;145setProfileState: (newState: UserProfile) => void;146errorMessage: string;147emailIsReadonly?: boolean;148user?: User;149children?: React.ReactChild[] | React.ReactChild;150}) {151return (152<div>153<p className="text-base text-gray-500 pb-4 max-w-xl">154The following information will be used to set up Git configuration. You can override Git author name and155email per project by using the default environment variables <code>GIT_AUTHOR_NAME</code>,{" "}156<code>GIT_COMMITTER_NAME</code>, <code>GIT_AUTHOR_EMAIL</code> and <code>GIT_COMMITTER_EMAIL</code>.157</p>158{props.errorMessage.length > 0 && (159<Alert type="error" closable={true} className="mb-2 max-w-xl rounded-md">160{props.errorMessage}161</Alert>162)}163<div className="flex flex-col lg:flex-row">164<fieldset>165<TextInputField166label="Name"167value={props.profileState.name}168onChange={(val) => props.setProfileState({ ...props.profileState, name: val })}169/>170<TextInputField171label="Email"172value={props.profileState.emailAddress || ""}173disabled={props.emailIsReadonly}174onChange={(val) => {175props.setProfileState({ ...props.profileState, emailAddress: val });176}}177/>178{props.user && (179<InputField label="User ID">180<InputWithCopy value={props.user.id} tip="Copy User ID" />181</InputField>182)}183</fieldset>184<div className="lg:pl-14">185<div className="mt-4">186<Subheading>Avatar</Subheading>187<img188className="rounded-full w-24 h-24"189src={props.profileState.avatarUrl}190alt={props.profileState.name}191/>192</div>193</div>194</div>195{props.children || null}196</div>197);198}199200201