Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/dashboard/src/user-settings/Preferences.tsx
2500 views
1
/**
2
* Copyright (c) 2021 Gitpod GmbH. All rights reserved.
3
* Licensed under the GNU Affero General Public License (AGPL).
4
* See License.AGPL.txt in the project root for license information.
5
*/
6
7
import { useCallback, useContext, useState } from "react";
8
import { getGitpodService } from "../service/service";
9
import { UserContext } from "../user-context";
10
import { PageWithSettingsSubMenu } from "./PageWithSettingsSubMenu";
11
import { ThemeSelector } from "../components/ThemeSelector";
12
import { Link } from "react-router-dom";
13
import { Heading2, Heading3, Subheading } from "../components/typography/headings";
14
import { Button } from "@podkit/buttons/Button";
15
import SelectIDE from "./SelectIDE";
16
import { InputField } from "../components/forms/InputField";
17
import { TextInput } from "../components/forms/TextInputField";
18
import { useToast } from "../components/toasts/Toasts";
19
import {
20
useUpdateCurrentUserDotfileRepoMutation,
21
useUpdateCurrentUserMutation,
22
} from "../data/current-user/update-mutation";
23
import { useOrgBillingMode } from "../data/billing-mode/org-billing-mode-query";
24
import { converter, userClient } from "../service/public-api";
25
import { LoadingButton } from "@podkit/buttons/LoadingButton";
26
import { useOrgSettingsQuery } from "../data/organizations/org-settings-query";
27
import Alert from "../components/Alert";
28
import { useDefaultOrgTimeoutQuery } from "../data/organizations/default-org-timeout-query";
29
30
export type IDEChangedTrackLocation = "workspace_list" | "workspace_start" | "preferences";
31
32
export default function Preferences() {
33
const { toast } = useToast();
34
const { user, setUser } = useContext(UserContext);
35
const updateUser = useUpdateCurrentUserMutation();
36
const billingMode = useOrgBillingMode();
37
const updateDotfileRepo = useUpdateCurrentUserDotfileRepoMutation();
38
const { data: settings } = useOrgSettingsQuery();
39
const defaultOrgTimeout = useDefaultOrgTimeoutQuery();
40
const [dotfileRepo, setDotfileRepo] = useState<string>(user?.dotfileRepo || "");
41
42
const [workspaceTimeout, setWorkspaceTimeout] = useState<string>(
43
converter.toDurationStringOpt(user?.workspaceTimeoutSettings?.inactivity) || "",
44
);
45
const [timeoutUpdating, setTimeoutUpdating] = useState(false);
46
const [creationError, setCreationError] = useState<Error>();
47
48
const saveDotfileRepo = useCallback(
49
async (e) => {
50
e.preventDefault();
51
52
const user = await updateDotfileRepo.mutateAsync(dotfileRepo);
53
if (user) {
54
setUser(user);
55
}
56
toast("Your dotfiles repository was updated.");
57
},
58
[updateDotfileRepo, dotfileRepo, setUser, toast],
59
);
60
61
const saveWorkspaceTimeout = useCallback(
62
async (e) => {
63
e.preventDefault();
64
setTimeoutUpdating(true);
65
66
// TODO: Convert this to a mutation
67
try {
68
await getGitpodService().server.updateWorkspaceTimeoutSetting({
69
workspaceTimeout: workspaceTimeout,
70
disabledClosedTimeout: workspaceTimeout === "" ? false : true,
71
});
72
73
// TODO: Once current user is in react-query, we can instead invalidate the query vs. refetching here
74
const { user } = await userClient.getAuthenticatedUser({});
75
if (user) {
76
setUser(user);
77
}
78
79
let toastMessage = <>Default workspace timeout was updated.</>;
80
if (billingMode.data?.mode === "usage-based") {
81
if (!billingMode.data.paid) {
82
toastMessage = (
83
<>
84
{toastMessage} Changes will only affect workspaces in paid organizations. Go to{" "}
85
<Link to="/billing" className="gp-link">
86
billing
87
</Link>{" "}
88
to upgrade your organization.
89
</>
90
);
91
}
92
}
93
// Reset creationError to avoid displaying the error message and toast the success message
94
setCreationError(undefined);
95
toast(toastMessage);
96
} catch (e) {
97
setCreationError(new Error(e.message));
98
} finally {
99
setTimeoutUpdating(false);
100
}
101
},
102
[toast, setUser, workspaceTimeout, billingMode],
103
);
104
105
const clearCreateWorkspaceOptions = useCallback(async () => {
106
if (!user) {
107
return;
108
}
109
const updatedUser = await updateUser.mutateAsync({
110
additionalData: {
111
workspaceAutostartOptions: [],
112
},
113
});
114
setUser(updatedUser);
115
toast("Workspace options have been cleared.");
116
}, [updateUser, setUser, toast, user]);
117
118
return (
119
<div>
120
<PageWithSettingsSubMenu>
121
<Heading2>New Workspaces</Heading2>
122
<Subheading>
123
Choose your default editor.{" "}
124
<a
125
className="gp-link"
126
href="https://www.gitpod.io/docs/references/ides-and-editors"
127
target="_blank"
128
rel="noreferrer"
129
>
130
Learn more
131
</a>
132
</Subheading>
133
<SelectIDE location="preferences" />
134
<Heading3 className="mt-12">Workspace Options</Heading3>
135
<Subheading>Clear last used options for creating workspaces.</Subheading>
136
<Button className="mt-4" variant="secondary" onClick={clearCreateWorkspaceOptions}>
137
Reset Options
138
</Button>
139
140
<ThemeSelector className="mt-12" />
141
142
<Heading2 className="mt-12">Dotfiles</Heading2>
143
<Subheading>Customize workspaces using dotfiles.</Subheading>
144
145
<form className="mt-4 max-w-xl" onSubmit={saveDotfileRepo}>
146
<InputField
147
label="Repository URL"
148
hint="Add a repository URL that includes dotfiles. Gitpod will clone and install your dotfiles for every new workspace."
149
>
150
<div className="flex space-x-2">
151
<div className="flex-grow">
152
<TextInput
153
value={dotfileRepo}
154
placeholder="e.g. https://github.com/username/dotfiles"
155
onChange={setDotfileRepo}
156
/>
157
</div>
158
<LoadingButton
159
type="submit"
160
loading={updateDotfileRepo.isLoading}
161
disabled={updateDotfileRepo.isLoading || dotfileRepo === user?.dotfileRepo}
162
>
163
Save
164
</LoadingButton>
165
</div>
166
</InputField>
167
</form>
168
169
<Heading2 className="mt-12">Timeouts</Heading2>
170
<Subheading>Workspaces will stop after a period of inactivity without any user input.</Subheading>
171
172
<div className="mt-4 max-w-xl">
173
{!!settings?.timeoutSettings?.denyUserTimeouts && (
174
<Alert type="warning" className="mb-4">
175
The currently selected organization does not allow members to set custom workspace timeouts,
176
so for workspaces created in it, its default timeout of{" "}
177
{converter.toDurationStringOpt(settings?.timeoutSettings?.inactivity) ?? defaultOrgTimeout}{" "}
178
will be used.
179
</Alert>
180
)}
181
182
<form onSubmit={saveWorkspaceTimeout}>
183
<InputField
184
label="Default Workspace Timeout"
185
hint={
186
<span>
187
Use minutes or hours, like <span className="font-semibold">30m</span> or{" "}
188
<span className="font-semibold">2h</span>
189
</span>
190
}
191
>
192
<div className="flex flex-col">
193
<div className="flex items-center space-x-2 mb-2">
194
<div className="flex-grow">
195
<TextInput
196
value={workspaceTimeout}
197
placeholder="e.g. 30m"
198
onChange={setWorkspaceTimeout}
199
/>
200
</div>
201
<LoadingButton
202
type="submit"
203
loading={timeoutUpdating}
204
disabled={
205
workspaceTimeout ===
206
(converter.toDurationStringOpt(
207
user?.workspaceTimeoutSettings?.inactivity,
208
) || "")
209
}
210
>
211
Save
212
</LoadingButton>
213
</div>
214
{creationError && (
215
<p className="text-gitpod-red w-full max-w-lg">
216
Cannot set custom workspace timeout: {creationError.message}
217
</p>
218
)}
219
</div>
220
</InputField>
221
</form>
222
</div>
223
</PageWithSettingsSubMenu>
224
</div>
225
);
226
}
227
228