|
1 |
| -import { ComponentProps, FC, useState } from "react" |
| 1 | +import { FC } from "react" |
2 | 2 | import { Section } from "../../../components/SettingsLayout/Section"
|
3 | 3 | import { AccountForm } from "../../../components/SettingsAccountForm/SettingsAccountForm"
|
4 | 4 | import { useAuth } from "components/AuthProvider/AuthProvider"
|
5 | 5 | import { useMe } from "hooks/useMe"
|
6 | 6 | import { usePermissions } from "hooks/usePermissions"
|
7 |
| -import TextField from "@mui/material/TextField" |
8 |
| -import Box from "@mui/material/Box" |
9 |
| -import GitHubIcon from "@mui/icons-material/GitHub" |
10 |
| -import KeyIcon from "@mui/icons-material/VpnKey" |
11 |
| -import Button from "@mui/material/Button" |
12 |
| -import { useLocation } from "react-router-dom" |
13 |
| -import { retrieveRedirect } from "utils/redirect" |
14 |
| -import Typography from "@mui/material/Typography" |
15 |
| -import { convertToOAUTH, getAuthMethods } from "api/api" |
16 |
| -import { AuthMethods, LoginType } from "api/typesGenerated" |
17 |
| -import Skeleton from "@mui/material/Skeleton" |
18 |
| -import { Stack } from "components/Stack/Stack" |
19 |
| -import { useMutation, useQuery } from "@tanstack/react-query" |
20 |
| -import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog" |
21 |
| -import { getErrorMessage } from "api/errors" |
22 |
| - |
23 |
| -type LoginTypeConfirmation = |
24 |
| - | { |
25 |
| - open: false |
26 |
| - selectedType: undefined |
27 |
| - } |
28 |
| - | { |
29 |
| - open: true |
30 |
| - selectedType: LoginType |
31 |
| - } |
32 | 7 |
|
33 | 8 | export const AccountPage: FC = () => {
|
34 | 9 | const [authState, authSend] = useAuth()
|
35 | 10 | const me = useMe()
|
36 | 11 | const permissions = usePermissions()
|
37 | 12 | const { updateProfileError } = authState.context
|
38 | 13 | const canEditUsers = permissions && permissions.updateUsers
|
39 |
| - const location = useLocation() |
40 |
| - const redirectTo = retrieveRedirect(location.search) |
41 |
| - const [loginTypeConfirmation, setLoginTypeConfirmation] = |
42 |
| - useState<LoginTypeConfirmation>({ open: false, selectedType: undefined }) |
43 |
| - const { data: authMethods } = useQuery({ |
44 |
| - queryKey: ["authMethods"], |
45 |
| - queryFn: getAuthMethods, |
46 |
| - }) |
47 |
| - const loginTypeMutation = useMutation(convertToOAUTH, { |
48 |
| - onSuccess: (data) => { |
49 |
| - window.location.href = `/api/v2/users/oidc/callback?oidc_merge_state=${ |
50 |
| - data.state_string |
51 |
| - }&redirect=${encodeURIComponent(redirectTo)}` |
52 |
| - }, |
53 |
| - }) |
54 | 14 |
|
55 | 15 | return (
|
56 |
| - <Stack spacing={8}> |
57 |
| - <Section title="Account" description="Update your account info"> |
58 |
| - <AccountForm |
59 |
| - editable={Boolean(canEditUsers)} |
60 |
| - email={me.email} |
61 |
| - updateProfileError={updateProfileError} |
62 |
| - isLoading={authState.matches("signedIn.profile.updatingProfile")} |
63 |
| - initialValues={{ |
64 |
| - username: me.username, |
65 |
| - }} |
66 |
| - onSubmit={(data) => { |
67 |
| - authSend({ |
68 |
| - type: "UPDATE_PROFILE", |
69 |
| - data, |
70 |
| - }) |
71 |
| - }} |
72 |
| - /> |
73 |
| - </Section> |
74 |
| - |
75 |
| - <Section |
76 |
| - title="Single Sign On" |
77 |
| - description="Authenticate in Coder using one-click" |
78 |
| - > |
79 |
| - <Box display="grid" gap="16px"> |
80 |
| - {authMethods ? ( |
81 |
| - authMethods.me_login_type === "password" ? ( |
82 |
| - <> |
83 |
| - {authMethods.github.enabled && ( |
84 |
| - <GitHubButton |
85 |
| - disabled={loginTypeMutation.isLoading} |
86 |
| - onClick={() => |
87 |
| - setLoginTypeConfirmation({ |
88 |
| - open: true, |
89 |
| - selectedType: "github", |
90 |
| - }) |
91 |
| - } |
92 |
| - > |
93 |
| - GitHub |
94 |
| - </GitHubButton> |
95 |
| - )} |
96 |
| - {authMethods.oidc.enabled && ( |
97 |
| - <OIDCButton |
98 |
| - authMethods={authMethods} |
99 |
| - disabled={loginTypeMutation.isLoading} |
100 |
| - onClick={() => |
101 |
| - setLoginTypeConfirmation({ |
102 |
| - open: true, |
103 |
| - selectedType: "oidc", |
104 |
| - }) |
105 |
| - } |
106 |
| - > |
107 |
| - {getOIDCLabel(authMethods)} |
108 |
| - </OIDCButton> |
109 |
| - )} |
110 |
| - </> |
111 |
| - ) : ( |
112 |
| - <> |
113 |
| - {authMethods.me_login_type === "github" && ( |
114 |
| - <GitHubButton disabled> |
115 |
| - Authenticated with GitHub |
116 |
| - </GitHubButton> |
117 |
| - )} |
118 |
| - |
119 |
| - {authMethods.me_login_type === "oidc" && ( |
120 |
| - <OIDCButton authMethods={authMethods} disabled> |
121 |
| - Authenticated with {getOIDCLabel(authMethods)} |
122 |
| - </OIDCButton> |
123 |
| - )} |
124 |
| - </> |
125 |
| - ) |
126 |
| - ) : ( |
127 |
| - <> |
128 |
| - <Skeleton |
129 |
| - variant="rectangular" |
130 |
| - sx={{ height: 40, borderRadius: 1 }} |
131 |
| - /> |
132 |
| - <Skeleton |
133 |
| - variant="rectangular" |
134 |
| - sx={{ height: 40, borderRadius: 1 }} |
135 |
| - /> |
136 |
| - </> |
137 |
| - )} |
138 |
| - </Box> |
139 |
| - </Section> |
140 |
| - |
141 |
| - <ConfirmLoginTypeChangeModal |
142 |
| - open={loginTypeConfirmation.open} |
143 |
| - error={loginTypeMutation.error} |
144 |
| - // We still want to show it loading when it is success so the modal is |
145 |
| - // not going to close or change until the oauth redirect |
146 |
| - loading={loginTypeMutation.isLoading || loginTypeMutation.isSuccess} |
147 |
| - onClose={() => { |
148 |
| - setLoginTypeConfirmation({ open: false, selectedType: undefined }) |
149 |
| - loginTypeMutation.reset() |
| 16 | + <Section title="Account" description="Update your account info"> |
| 17 | + <AccountForm |
| 18 | + editable={Boolean(canEditUsers)} |
| 19 | + email={me.email} |
| 20 | + updateProfileError={updateProfileError} |
| 21 | + isLoading={authState.matches("signedIn.profile.updatingProfile")} |
| 22 | + initialValues={{ |
| 23 | + username: me.username, |
150 | 24 | }}
|
151 |
| - onConfirm={(password) => { |
152 |
| - if (!loginTypeConfirmation.selectedType) { |
153 |
| - throw new Error("No login type selected") |
154 |
| - } |
155 |
| - loginTypeMutation.mutate({ |
156 |
| - to_login_type: loginTypeConfirmation.selectedType, |
157 |
| - email: me.email, |
158 |
| - password, |
| 25 | + onSubmit={(data) => { |
| 26 | + authSend({ |
| 27 | + type: "UPDATE_PROFILE", |
| 28 | + data, |
159 | 29 | })
|
160 | 30 | }}
|
161 | 31 | />
|
162 |
| - </Stack> |
163 |
| - ) |
164 |
| -} |
165 |
| - |
166 |
| -const GitHubButton = (props: ComponentProps<typeof Button>) => { |
167 |
| - return ( |
168 |
| - <Button |
169 |
| - startIcon={<GitHubIcon sx={{ width: 16, height: 16 }} />} |
170 |
| - fullWidth |
171 |
| - type="submit" |
172 |
| - size="large" |
173 |
| - {...props} |
174 |
| - /> |
175 |
| - ) |
176 |
| -} |
177 |
| - |
178 |
| -const OIDCButton = ({ |
179 |
| - authMethods, |
180 |
| - ...buttonProps |
181 |
| -}: ComponentProps<typeof Button> & { authMethods: AuthMethods }) => { |
182 |
| - return ( |
183 |
| - <Button |
184 |
| - size="large" |
185 |
| - startIcon={ |
186 |
| - authMethods.oidc.iconUrl ? ( |
187 |
| - <Box |
188 |
| - component="img" |
189 |
| - alt="Open ID Connect icon" |
190 |
| - src={authMethods.oidc.iconUrl} |
191 |
| - sx={{ width: 16, height: 16 }} |
192 |
| - /> |
193 |
| - ) : ( |
194 |
| - <KeyIcon sx={{ width: 16, height: 16 }} /> |
195 |
| - ) |
196 |
| - } |
197 |
| - fullWidth |
198 |
| - type="submit" |
199 |
| - {...buttonProps} |
200 |
| - /> |
201 |
| - ) |
202 |
| -} |
203 |
| - |
204 |
| -const getOIDCLabel = (authMethods: AuthMethods) => { |
205 |
| - return authMethods.oidc.signInText || "OpenID Connect" |
206 |
| -} |
207 |
| - |
208 |
| -const ConfirmLoginTypeChangeModal = ({ |
209 |
| - open, |
210 |
| - loading, |
211 |
| - error, |
212 |
| - onClose, |
213 |
| - onConfirm, |
214 |
| -}: { |
215 |
| - open: boolean |
216 |
| - loading: boolean |
217 |
| - error: unknown |
218 |
| - onClose: () => void |
219 |
| - onConfirm: (password: string) => void |
220 |
| -}) => { |
221 |
| - const [password, setPassword] = useState("") |
222 |
| - |
223 |
| - const handleConfirm = () => { |
224 |
| - onConfirm(password) |
225 |
| - } |
226 |
| - |
227 |
| - return ( |
228 |
| - <ConfirmDialog |
229 |
| - open={open} |
230 |
| - onClose={() => { |
231 |
| - onClose() |
232 |
| - }} |
233 |
| - onConfirm={handleConfirm} |
234 |
| - hideCancel={false} |
235 |
| - cancelText="Cancel" |
236 |
| - confirmText="Update" |
237 |
| - title="Change login type" |
238 |
| - confirmLoading={loading} |
239 |
| - description={ |
240 |
| - <Stack> |
241 |
| - <Typography> |
242 |
| - After changing your login type, you will not be able to change it |
243 |
| - again. Are you sure you want to proceed and change your login type? |
244 |
| - </Typography> |
245 |
| - <TextField |
246 |
| - autoFocus |
247 |
| - onKeyDown={(event) => { |
248 |
| - if (event.key === "Enter") { |
249 |
| - handleConfirm() |
250 |
| - } |
251 |
| - }} |
252 |
| - error={Boolean(error)} |
253 |
| - helperText={ |
254 |
| - error |
255 |
| - ? getErrorMessage(error, "Your password is incorrect") |
256 |
| - : undefined |
257 |
| - } |
258 |
| - name="confirm-password" |
259 |
| - id="confirm-password" |
260 |
| - value={password} |
261 |
| - onChange={(e) => setPassword(e.currentTarget.value)} |
262 |
| - label="Confirm your password" |
263 |
| - type="password" |
264 |
| - /> |
265 |
| - </Stack> |
266 |
| - } |
267 |
| - /> |
| 32 | + </Section> |
268 | 33 | )
|
269 | 34 | }
|
270 | 35 |
|
|
0 commit comments