Path: blob/main/components/dashboard/src/user-settings/Preferences.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 } from "../service/service";8import { UserContext } from "../user-context";9import { PageWithSettingsSubMenu } from "./PageWithSettingsSubMenu";10import { ThemeSelector } from "../components/ThemeSelector";11import { Link } from "react-router-dom";12import { Heading2, Heading3, Subheading } from "../components/typography/headings";13import { Button } from "@podkit/buttons/Button";14import SelectIDE from "./SelectIDE";15import { InputField } from "../components/forms/InputField";16import { TextInput } from "../components/forms/TextInputField";17import { useToast } from "../components/toasts/Toasts";18import {19useUpdateCurrentUserDotfileRepoMutation,20useUpdateCurrentUserMutation,21} from "../data/current-user/update-mutation";22import { useOrgBillingMode } from "../data/billing-mode/org-billing-mode-query";23import { converter, userClient } from "../service/public-api";24import { LoadingButton } from "@podkit/buttons/LoadingButton";25import { useOrgSettingsQuery } from "../data/organizations/org-settings-query";26import Alert from "../components/Alert";27import { useDefaultOrgTimeoutQuery } from "../data/organizations/default-org-timeout-query";2829export type IDEChangedTrackLocation = "workspace_list" | "workspace_start" | "preferences";3031export default function Preferences() {32const { toast } = useToast();33const { user, setUser } = useContext(UserContext);34const updateUser = useUpdateCurrentUserMutation();35const billingMode = useOrgBillingMode();36const updateDotfileRepo = useUpdateCurrentUserDotfileRepoMutation();37const { data: settings } = useOrgSettingsQuery();38const defaultOrgTimeout = useDefaultOrgTimeoutQuery();39const [dotfileRepo, setDotfileRepo] = useState<string>(user?.dotfileRepo || "");4041const [workspaceTimeout, setWorkspaceTimeout] = useState<string>(42converter.toDurationStringOpt(user?.workspaceTimeoutSettings?.inactivity) || "",43);44const [timeoutUpdating, setTimeoutUpdating] = useState(false);45const [creationError, setCreationError] = useState<Error>();4647const saveDotfileRepo = useCallback(48async (e) => {49e.preventDefault();5051const user = await updateDotfileRepo.mutateAsync(dotfileRepo);52if (user) {53setUser(user);54}55toast("Your dotfiles repository was updated.");56},57[updateDotfileRepo, dotfileRepo, setUser, toast],58);5960const saveWorkspaceTimeout = useCallback(61async (e) => {62e.preventDefault();63setTimeoutUpdating(true);6465// TODO: Convert this to a mutation66try {67await getGitpodService().server.updateWorkspaceTimeoutSetting({68workspaceTimeout: workspaceTimeout,69disabledClosedTimeout: workspaceTimeout === "" ? false : true,70});7172// TODO: Once current user is in react-query, we can instead invalidate the query vs. refetching here73const { user } = await userClient.getAuthenticatedUser({});74if (user) {75setUser(user);76}7778let toastMessage = <>Default workspace timeout was updated.</>;79if (billingMode.data?.mode === "usage-based") {80if (!billingMode.data.paid) {81toastMessage = (82<>83{toastMessage} Changes will only affect workspaces in paid organizations. Go to{" "}84<Link to="/billing" className="gp-link">85billing86</Link>{" "}87to upgrade your organization.88</>89);90}91}92// Reset creationError to avoid displaying the error message and toast the success message93setCreationError(undefined);94toast(toastMessage);95} catch (e) {96setCreationError(new Error(e.message));97} finally {98setTimeoutUpdating(false);99}100},101[toast, setUser, workspaceTimeout, billingMode],102);103104const clearCreateWorkspaceOptions = useCallback(async () => {105if (!user) {106return;107}108const updatedUser = await updateUser.mutateAsync({109additionalData: {110workspaceAutostartOptions: [],111},112});113setUser(updatedUser);114toast("Workspace options have been cleared.");115}, [updateUser, setUser, toast, user]);116117return (118<div>119<PageWithSettingsSubMenu>120<Heading2>New Workspaces</Heading2>121<Subheading>122Choose your default editor.{" "}123<a124className="gp-link"125href="https://www.gitpod.io/docs/references/ides-and-editors"126target="_blank"127rel="noreferrer"128>129Learn more130</a>131</Subheading>132<SelectIDE location="preferences" />133<Heading3 className="mt-12">Workspace Options</Heading3>134<Subheading>Clear last used options for creating workspaces.</Subheading>135<Button className="mt-4" variant="secondary" onClick={clearCreateWorkspaceOptions}>136Reset Options137</Button>138139<ThemeSelector className="mt-12" />140141<Heading2 className="mt-12">Dotfiles</Heading2>142<Subheading>Customize workspaces using dotfiles.</Subheading>143144<form className="mt-4 max-w-xl" onSubmit={saveDotfileRepo}>145<InputField146label="Repository URL"147hint="Add a repository URL that includes dotfiles. Gitpod will clone and install your dotfiles for every new workspace."148>149<div className="flex space-x-2">150<div className="flex-grow">151<TextInput152value={dotfileRepo}153placeholder="e.g. https://github.com/username/dotfiles"154onChange={setDotfileRepo}155/>156</div>157<LoadingButton158type="submit"159loading={updateDotfileRepo.isLoading}160disabled={updateDotfileRepo.isLoading || dotfileRepo === user?.dotfileRepo}161>162Save163</LoadingButton>164</div>165</InputField>166</form>167168<Heading2 className="mt-12">Timeouts</Heading2>169<Subheading>Workspaces will stop after a period of inactivity without any user input.</Subheading>170171<div className="mt-4 max-w-xl">172{!!settings?.timeoutSettings?.denyUserTimeouts && (173<Alert type="warning" className="mb-4">174The currently selected organization does not allow members to set custom workspace timeouts,175so for workspaces created in it, its default timeout of{" "}176{converter.toDurationStringOpt(settings?.timeoutSettings?.inactivity) ?? defaultOrgTimeout}{" "}177will be used.178</Alert>179)}180181<form onSubmit={saveWorkspaceTimeout}>182<InputField183label="Default Workspace Timeout"184hint={185<span>186Use minutes or hours, like <span className="font-semibold">30m</span> or{" "}187<span className="font-semibold">2h</span>188</span>189}190>191<div className="flex flex-col">192<div className="flex items-center space-x-2 mb-2">193<div className="flex-grow">194<TextInput195value={workspaceTimeout}196placeholder="e.g. 30m"197onChange={setWorkspaceTimeout}198/>199</div>200<LoadingButton201type="submit"202loading={timeoutUpdating}203disabled={204workspaceTimeout ===205(converter.toDurationStringOpt(206user?.workspaceTimeoutSettings?.inactivity,207) || "")208}209>210Save211</LoadingButton>212</div>213{creationError && (214<p className="text-gitpod-red w-full max-w-lg">215Cannot set custom workspace timeout: {creationError.message}216</p>217)}218</div>219</InputField>220</form>221</div>222</PageWithSettingsSubMenu>223</div>224);225}226227228