-
Notifications
You must be signed in to change notification settings - Fork 5
feat: add auth styling and 'extra actions' menu #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
5724aee
feat: add auth styling and 'extra actions' menu
Parkreiner a62bc1d
fix: make sure auth field has valid label
Parkreiner 411a017
docs: rewrite comment for clarity
Parkreiner 6eeef91
refactor: rename reauthenticating status to authenticating
Parkreiner b9e9765
fix: make sure pressing Enter doesn't cause button edge case behavior
Parkreiner b5a256e
fix: clean up form semantics to protect against more edge cases
Parkreiner 6fd74b4
refactor: rewrite code to have more explicit exhaustiveness checks
Parkreiner ad729e1
refactor: update style declarations to be less confusing
Parkreiner 75d410f
fix: make sure wrapper is only used when needed
Parkreiner 96b9f7f
fix: make it more clear that clear button is interactive
Parkreiner 2d0939e
fix: update spelling to follow conventional standards
Parkreiner 95e483a
fix: add horizontal padding for search bar reminder
Parkreiner 705e267
fix: prevent infinite loops when proxied deployment is unavailable
Parkreiner 9d9d45d
chore: add 'deploymentUnavailable' status to auth
Parkreiner 676c538
fix: update test helpers to use newer testing library versions
Parkreiner c037d8d
fix: polyfill AbortSignal.timeout for tests
Parkreiner c142349
fix: resolve all test flakes
Parkreiner be495b9
chore: remove unneeded package dependencies
Parkreiner File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
60 changes: 60 additions & 0 deletions
60
plugins/backstage-plugin-coder/src/components/CoderAuthWrapper/CoderAuthDistrustedForm.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import React from 'react'; | ||
import { CoderLogo } from '../CoderLogo'; | ||
import { LinkButton } from '@backstage/core-components'; | ||
import { makeStyles } from '@material-ui/core'; | ||
import { useCoderAuth } from '../CoderProvider'; | ||
|
||
const useStyles = makeStyles(theme => ({ | ||
root: { | ||
display: 'flex', | ||
flexFlow: 'column nowrap', | ||
alignItems: 'center', | ||
maxWidth: '30em', | ||
marginLeft: 'auto', | ||
marginRight: 'auto', | ||
rowGap: theme.spacing(2), | ||
}, | ||
|
||
button: { | ||
maxWidth: 'fit-content', | ||
marginLeft: 'auto', | ||
marginRight: 'auto', | ||
}, | ||
|
||
coderLogo: { | ||
display: 'block', | ||
width: 'fit-content', | ||
marginLeft: 'auto', | ||
marginRight: 'auto', | ||
}, | ||
})); | ||
|
||
export const CoderAuthDistrustedForm = () => { | ||
const styles = useStyles(); | ||
const { ejectToken } = useCoderAuth(); | ||
|
||
return ( | ||
<div className={styles.root}> | ||
<div> | ||
<CoderLogo className={styles.coderLogo} /> | ||
<p> | ||
Unable to verify token authenticity. Please check your internet | ||
connection, or try ejecting the token. | ||
</p> | ||
</div> | ||
|
||
<LinkButton | ||
disableRipple | ||
to="" | ||
component="button" | ||
type="submit" | ||
color="primary" | ||
variant="contained" | ||
className={styles.button} | ||
onClick={ejectToken} | ||
> | ||
Eject token | ||
</LinkButton> | ||
</div> | ||
); | ||
}; |
181 changes: 181 additions & 0 deletions
181
plugins/backstage-plugin-coder/src/components/CoderAuthWrapper/CoderAuthInputForm.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
import React, { FormEvent } from 'react'; | ||
import { useId } from '../../hooks/hookPolyfills'; | ||
import { | ||
type CoderAuthStatus, | ||
useCoderAppConfig, | ||
useCoderAuth, | ||
} from '../CoderProvider'; | ||
|
||
import { Theme, makeStyles } from '@material-ui/core'; | ||
import TextField from '@material-ui/core/TextField'; | ||
import { CoderLogo } from '../CoderLogo'; | ||
import { Link, LinkButton } from '@backstage/core-components'; | ||
import { VisuallyHidden } from '../VisuallyHidden'; | ||
|
||
type UseStyleInput = Readonly<{ status: CoderAuthStatus }>; | ||
type StyleKeys = | ||
| 'formContainer' | ||
| 'authInputFieldset' | ||
| 'coderLogo' | ||
| 'authButton' | ||
| 'warningBanner' | ||
| 'warningBannerContainer'; | ||
|
||
const useStyles = makeStyles<Theme, UseStyleInput, StyleKeys>(theme => ({ | ||
formContainer: { | ||
maxWidth: '30em', | ||
marginLeft: 'auto', | ||
marginRight: 'auto', | ||
}, | ||
|
||
authInputFieldset: { | ||
display: 'flex', | ||
flexFlow: 'column nowrap', | ||
rowGap: theme.spacing(2), | ||
margin: `${theme.spacing(-0.5)} 0 0 0`, | ||
border: 'none', | ||
padding: 0, | ||
}, | ||
|
||
coderLogo: { | ||
display: 'block', | ||
width: 'fit-content', | ||
marginLeft: 'auto', | ||
marginRight: 'auto', | ||
}, | ||
|
||
authButton: { | ||
display: 'block', | ||
width: 'fit-content', | ||
marginLeft: 'auto', | ||
marginRight: 'auto', | ||
}, | ||
|
||
warningBannerContainer: { | ||
paddingTop: theme.spacing(4), | ||
paddingLeft: theme.spacing(6), | ||
paddingRight: theme.spacing(6), | ||
}, | ||
|
||
warningBanner: ({ status }) => { | ||
let color: string; | ||
let backgroundColor: string; | ||
|
||
if (status === 'invalid') { | ||
color = theme.palette.error.contrastText; | ||
backgroundColor = theme.palette.banner.error; | ||
} else { | ||
color = theme.palette.text.primary; | ||
backgroundColor = theme.palette.background.default; | ||
} | ||
|
||
return { | ||
color, | ||
backgroundColor, | ||
borderRadius: theme.shape.borderRadius, | ||
textAlign: 'center', | ||
paddingTop: theme.spacing(0.5), | ||
paddingBottom: theme.spacing(0.5), | ||
}; | ||
}, | ||
})); | ||
|
||
export const CoderAuthInputForm = () => { | ||
const hookId = useId(); | ||
const appConfig = useCoderAppConfig(); | ||
const { status, registerNewToken } = useCoderAuth(); | ||
const styles = useStyles({ status }); | ||
|
||
const onSubmit = (event: FormEvent<HTMLFormElement>) => { | ||
event.preventDefault(); | ||
const formData = Object.fromEntries(new FormData(event.currentTarget)); | ||
const newToken = | ||
typeof formData.authToken === 'string' ? formData.authToken : ''; | ||
|
||
registerNewToken(newToken); | ||
}; | ||
|
||
const legendId = `${hookId}-legend`; | ||
const authTokenInputId = `${hookId}-auth-token`; | ||
const warningBannerId = `${hookId}-warning-banner`; | ||
|
||
return ( | ||
<form className={styles.formContainer} onSubmit={onSubmit}> | ||
<div> | ||
<CoderLogo className={styles.coderLogo} /> | ||
<p> | ||
Your Coder session token is {mapAuthStatusToText(status)}. Please | ||
enter a new token from your{' '} | ||
<Link | ||
to={`${appConfig.deployment.accessUrl}/cli-auth`} | ||
target="_blank" | ||
> | ||
Coder deployment's token page | ||
<VisuallyHidden> (link opens in new tab)</VisuallyHidden> | ||
</Link> | ||
. | ||
</p> | ||
</div> | ||
|
||
<fieldset className={styles.authInputFieldset} aria-labelledby={legendId}> | ||
<legend hidden id={legendId}> | ||
Auth input | ||
</legend> | ||
|
||
<TextField | ||
// Adding the label prop directly to the TextField will place a label | ||
// in the HTML, so sighted users are fine. But for some reason, it | ||
// won't connect the label and input together, which breaks | ||
// accessibility for screen readers. Need to wire up extra IDs, sadly. | ||
label="Auth token" | ||
id={authTokenInputId} | ||
InputLabelProps={{ htmlFor: authTokenInputId }} | ||
required | ||
name="authToken" | ||
type="password" | ||
defaultValue="" | ||
aria-errormessage={warningBannerId} | ||
style={{ width: '100%' }} | ||
/> | ||
|
||
<LinkButton | ||
disableRipple | ||
to="" | ||
component="button" | ||
type="submit" | ||
color="primary" | ||
variant="contained" | ||
className={styles.authButton} | ||
> | ||
Authenticate | ||
</LinkButton> | ||
</fieldset> | ||
|
||
{(status === 'invalid' || status === 'authenticating') && ( | ||
<div className={styles.warningBannerContainer}> | ||
<div id={warningBannerId} className={styles.warningBanner}> | ||
{status === 'invalid' && 'Invalid token'} | ||
{status === 'authenticating' && <>Authenticating…</>} | ||
</div> | ||
</div> | ||
)} | ||
</form> | ||
); | ||
}; | ||
|
||
function mapAuthStatusToText(status: CoderAuthStatus): string { | ||
switch (status) { | ||
case 'tokenMissing': { | ||
return 'missing'; | ||
} | ||
|
||
case 'initializing': | ||
case 'authenticating': { | ||
return status; | ||
} | ||
|
||
default: { | ||
return 'invalid'; | ||
} | ||
} | ||
} |
62 changes: 62 additions & 0 deletions
62
plugins/backstage-plugin-coder/src/components/CoderAuthWrapper/CoderAuthLoadingState.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import React, { useEffect, useState } from 'react'; | ||
import { CoderLogo } from '../CoderLogo'; | ||
import { makeStyles } from '@material-ui/core'; | ||
import { VisuallyHidden } from '../VisuallyHidden'; | ||
|
||
const MAX_DOTS = 3; | ||
const dotRange = new Array(MAX_DOTS).fill(null).map((_, i) => i + 1); | ||
|
||
const useStyles = makeStyles(theme => ({ | ||
root: { | ||
display: 'flex', | ||
flexFlow: 'column nowrap', | ||
alignItems: 'center', | ||
}, | ||
|
||
text: { | ||
lineHeight: theme.typography.body1.lineHeight, | ||
paddingLeft: theme.spacing(1), | ||
}, | ||
|
||
coderLogo: { | ||
display: 'block', | ||
width: 'fit-content', | ||
marginLeft: 'auto', | ||
marginRight: 'auto', | ||
}, | ||
})); | ||
|
||
export const CoderAuthLoadingState = () => { | ||
const [visibleDots, setVisibleDots] = useState(0); | ||
const styles = useStyles(); | ||
|
||
useEffect(() => { | ||
const intervalId = window.setInterval(() => { | ||
setVisibleDots(current => (current + 1) % (MAX_DOTS + 1)); | ||
}, 1_000); | ||
|
||
return () => window.clearInterval(intervalId); | ||
}, []); | ||
|
||
return ( | ||
<div className={styles.root}> | ||
<CoderLogo className={styles.coderLogo} /> | ||
<p className={styles.text}> | ||
Loading | ||
{/* Exposing the more semantic ellipses for screen readers, but | ||
rendering the individual dots for sighted viewers so that they can | ||
be animated */} | ||
<VisuallyHidden>…</VisuallyHidden> | ||
{dotRange.map(dotPosition => ( | ||
<span | ||
key={dotPosition} | ||
style={{ opacity: visibleDots >= dotPosition ? 1 : 0 }} | ||
aria-hidden | ||
> | ||
. | ||
</span> | ||
))} | ||
</p> | ||
</div> | ||
); | ||
}; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this! Having the user choose whether to discard the token or not makes a lot of sense.