Skip to content

Commit e9ba719

Browse files
committed
Add basic flow using mocks
1 parent 43bca98 commit e9ba719

File tree

4 files changed

+218
-20
lines changed

4 files changed

+218
-20
lines changed

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,5 +212,5 @@
212212
// We often use a version of TypeScript that's ahead of the version shipped
213213
// with VS Code.
214214
"typescript.tsdk": "./site/node_modules/typescript/lib",
215-
"prettier.prettierPath": "./node_modules/prettier"
215+
"prettier.prettierPath": "./site/node_modules/prettier"
216216
}

site/src/components/SettingsAccountForm/SettingsAccountForm.test.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ describe("AccountForm", () => {
2424
onSubmit={() => {
2525
return
2626
}}
27+
onChangeToOIDCAuth={() => {
28+
return
29+
}}
2730
/>,
2831
)
2932

@@ -54,6 +57,9 @@ describe("AccountForm", () => {
5457
onSubmit={() => {
5558
return
5659
}}
60+
onChangeToOIDCAuth={() => {
61+
return
62+
}}
5763
/>,
5864
)
5965

site/src/components/SettingsAccountForm/SettingsAccountForm.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {
1010
import { LoadingButton } from "../LoadingButton/LoadingButton"
1111
import { ErrorAlert } from "components/Alert/ErrorAlert"
1212
import { Form, FormFields } from "components/Form/Form"
13+
import { Stack } from "components/Stack/Stack"
14+
import Button from "@mui/material/Button"
1315

1416
export interface AccountFormValues {
1517
username: string
@@ -34,6 +36,7 @@ export interface AccountFormProps {
3436
updateProfileError?: Error | unknown
3537
// initialTouched is only used for testing the error state of the form.
3638
initialTouched?: FormikTouched<AccountFormValues>
39+
onChangeToOIDCAuth: () => void
3740
}
3841

3942
export const AccountForm: FC<React.PropsWithChildren<AccountFormProps>> = ({
@@ -44,6 +47,7 @@ export const AccountForm: FC<React.PropsWithChildren<AccountFormProps>> = ({
4447
initialValues,
4548
updateProfileError,
4649
initialTouched,
50+
onChangeToOIDCAuth
4751
}) => {
4852
const form: FormikContextType<AccountFormValues> =
4953
useFormik<AccountFormValues>({
@@ -80,7 +84,7 @@ export const AccountForm: FC<React.PropsWithChildren<AccountFormProps>> = ({
8084
label={Language.usernameLabel}
8185
/>
8286

83-
<div>
87+
<Stack direction="row">
8488
<LoadingButton
8589
loading={isLoading}
8690
aria-disabled={!editable}
@@ -90,7 +94,9 @@ export const AccountForm: FC<React.PropsWithChildren<AccountFormProps>> = ({
9094
>
9195
{isLoading ? "" : Language.updateSettings}
9296
</LoadingButton>
93-
</div>
97+
98+
<Button type="button" onClick={onChangeToOIDCAuth}>Use OIDC to authenticate</Button>
99+
</Stack>
94100
</FormFields>
95101
</Form>
96102
</>

site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx

Lines changed: 203 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,221 @@
1-
import { FC } from "react"
1+
import { FC, useState } from "react"
22
import { Section } from "../../../components/SettingsLayout/Section"
33
import { AccountForm } from "../../../components/SettingsAccountForm/SettingsAccountForm"
44
import { useAuth } from "components/AuthProvider/AuthProvider"
55
import { useMe } from "hooks/useMe"
66
import { usePermissions } from "hooks/usePermissions"
7+
import { Dialog } from "components/Dialogs/Dialog"
8+
import TextField from "@mui/material/TextField"
9+
import { FormFields, VerticalForm } from "components/Form/Form"
10+
import { LoadingButton } from "components/LoadingButton/LoadingButton"
11+
import Box from "@mui/material/Box"
12+
import GitHubIcon from "@mui/icons-material/GitHub"
13+
import KeyIcon from "@mui/icons-material/VpnKey"
14+
import Button from "@mui/material/Button"
15+
import { MockAuthMethods } from "testHelpers/entities"
16+
import CircularProgress from "@mui/material/CircularProgress"
17+
import { useLocation } from "react-router-dom"
18+
import { retrieveRedirect } from "utils/redirect"
19+
import Typography from "@mui/material/Typography"
20+
21+
type OIDCState =
22+
| { status: "closed" }
23+
| { status: "confirmPassword"; error?: unknown }
24+
| { status: "confirmingPassword" }
25+
| { status: "selectOIDCProvider" }
26+
| { status: "updatingProvider" }
727

828
export const AccountPage: FC = () => {
929
const [authState, authSend] = useAuth()
1030
const me = useMe()
1131
const permissions = usePermissions()
1232
const { updateProfileError } = authState.context
1333
const canEditUsers = permissions && permissions.updateUsers
34+
const [OIDCState, setOIDCState] = useState<OIDCState>({
35+
status: "closed",
36+
})
37+
const location = useLocation()
38+
const redirectTo = retrieveRedirect(location.search)
1439

1540
return (
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,
24-
}}
25-
onSubmit={(data) => {
26-
authSend({
27-
type: "UPDATE_PROFILE",
28-
data,
29-
})
30-
}}
41+
<>
42+
<Section title="Account" description="Update your account info">
43+
<AccountForm
44+
editable={Boolean(canEditUsers)}
45+
email={me.email}
46+
updateProfileError={updateProfileError}
47+
isLoading={authState.matches("signedIn.profile.updatingProfile")}
48+
initialValues={{
49+
username: me.username,
50+
}}
51+
onSubmit={(data) => {
52+
authSend({
53+
type: "UPDATE_PROFILE",
54+
data,
55+
})
56+
}}
57+
onChangeToOIDCAuth={() => {
58+
setOIDCState({ status: "confirmPassword" })
59+
}}
60+
/>
61+
</Section>
62+
<OIDCChangeModal
63+
redirectTo={redirectTo}
64+
state={OIDCState}
65+
onChangeState={setOIDCState}
3166
/>
32-
</Section>
67+
</>
68+
)
69+
}
70+
71+
const OIDCChangeModal = ({
72+
state,
73+
onChangeState,
74+
redirectTo,
75+
}: {
76+
redirectTo: string
77+
state: OIDCState
78+
onChangeState: (newState: OIDCState) => void
79+
}) => {
80+
const authMethods = MockAuthMethods
81+
82+
const updateProvider = (provider: string) => {
83+
onChangeState({ status: "updatingProvider" })
84+
setTimeout(() => {
85+
window.location.href = `/api/v2/users/oidc/callback?oidc_merge_state=something&redirect=${encodeURIComponent(
86+
redirectTo,
87+
)}`
88+
}, 1000)
89+
}
90+
91+
return (
92+
<Dialog
93+
open={state.status !== "closed"}
94+
onClose={() => onChangeState({ status: "closed" })}
95+
sx={{
96+
"& .MuiPaper-root": {
97+
padding: (theme) => theme.spacing(5),
98+
backgroundColor: (theme) => theme.palette.background.paper,
99+
border: (theme) => `1px solid ${theme.palette.divider}`,
100+
width: 440,
101+
},
102+
}}
103+
>
104+
{(state.status === "confirmPassword" ||
105+
state.status === "confirmingPassword") && (
106+
<div>
107+
<Typography component="h3" sx={{ fontSize: 20 }}>
108+
Confirm password
109+
</Typography>
110+
<Typography
111+
component="p"
112+
sx={{
113+
color: (theme) => theme.palette.text.secondary,
114+
mt: 1,
115+
mb: 3,
116+
}}
117+
>
118+
We need to confirm your identity in order to proceed with the
119+
authentication changes
120+
</Typography>
121+
<VerticalForm
122+
onSubmit={async (e) => {
123+
e.preventDefault()
124+
onChangeState({ status: "confirmingPassword" })
125+
await new Promise((resolve) => setTimeout(resolve, 1000))
126+
onChangeState({ status: "selectOIDCProvider" })
127+
}}
128+
>
129+
<FormFields>
130+
<TextField
131+
type="password"
132+
label="Password"
133+
name="password"
134+
autoFocus
135+
required
136+
/>
137+
<LoadingButton
138+
size="large"
139+
type="submit"
140+
variant="contained"
141+
loading={state.status === "confirmingPassword"}
142+
>
143+
Confirm password
144+
</LoadingButton>
145+
</FormFields>
146+
</VerticalForm>
147+
</div>
148+
)}
149+
150+
{(state.status === "selectOIDCProvider" ||
151+
state.status === "updatingProvider") && (
152+
<div>
153+
<Typography component="h3" sx={{ fontSize: 20 }}>
154+
Select a provider
155+
</Typography>
156+
<Typography
157+
component="p"
158+
sx={{
159+
color: (theme) => theme.palette.text.secondary,
160+
mt: 1,
161+
mb: 3,
162+
}}
163+
>
164+
After selecting the provider, you will be redirected to the
165+
provider&lsquo;s authentication page.
166+
</Typography>
167+
<Box display="grid" gap="16px">
168+
<Button
169+
disabled={state.status === "updatingProvider"}
170+
onClick={() => updateProvider("github")}
171+
startIcon={<GitHubIcon sx={{ width: 16, height: 16 }} />}
172+
fullWidth
173+
type="submit"
174+
size="large"
175+
>
176+
GitHub
177+
</Button>
178+
<Button
179+
disabled={state.status === "updatingProvider"}
180+
onClick={() => updateProvider("oidc")}
181+
size="large"
182+
startIcon={
183+
authMethods.oidc.iconUrl ? (
184+
<Box
185+
component="img"
186+
alt="Open ID Connect icon"
187+
src={authMethods.oidc.iconUrl}
188+
sx={{ width: 16, height: 16 }}
189+
/>
190+
) : (
191+
<KeyIcon sx={{ width: 16, height: 16 }} />
192+
)
193+
}
194+
fullWidth
195+
type="submit"
196+
>
197+
{authMethods.oidc.signInText || "OpenID Connect"}
198+
</Button>
199+
</Box>
200+
{state.status === "updatingProvider" && (
201+
<Box
202+
sx={{
203+
display: "flex",
204+
alignItems: "center",
205+
justifyContent: "center",
206+
mt: (theme) => theme.spacing(2),
207+
gap: 1,
208+
fontSize: 13,
209+
color: (theme) => theme.palette.text.secondary,
210+
}}
211+
>
212+
<CircularProgress size={12} />
213+
Updating authentication method...
214+
</Box>
215+
)}
216+
</div>
217+
)}
218+
</Dialog>
33219
)
34220
}
35221

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy