|
1 | | -import React, { FormEvent } from 'react'; |
| 1 | +import React, { type FormEvent, useState } from 'react'; |
2 | 2 | import { useId } from '../../hooks/hookPolyfills'; |
3 | 3 | import { |
4 | 4 | type CoderAuthStatus, |
5 | 5 | useCoderAppConfig, |
6 | 6 | useCoderAuth, |
7 | 7 | } from '../CoderProvider'; |
8 | 8 |
|
9 | | -import { Theme, makeStyles } from '@material-ui/core'; |
10 | | -import TextField from '@material-ui/core/TextField'; |
11 | 9 | import { CoderLogo } from '../CoderLogo'; |
12 | 10 | import { Link, LinkButton } from '@backstage/core-components'; |
13 | 11 | import { VisuallyHidden } from '../VisuallyHidden'; |
| 12 | +import { makeStyles } from '@material-ui/core'; |
| 13 | +import TextField from '@material-ui/core/TextField'; |
| 14 | +import ErrorIcon from '@material-ui/icons/ErrorOutline'; |
| 15 | +import SyncIcon from '@material-ui/icons/Sync'; |
14 | 16 |
|
15 | | -type UseStyleInput = Readonly<{ status: CoderAuthStatus }>; |
16 | | -type StyleKeys = |
17 | | - | 'formContainer' |
18 | | - | 'authInputFieldset' |
19 | | - | 'coderLogo' |
20 | | - | 'authButton' |
21 | | - | 'warningBanner' |
22 | | - | 'warningBannerContainer'; |
23 | | - |
24 | | -const useStyles = makeStyles<Theme, UseStyleInput, StyleKeys>(theme => ({ |
| 17 | +const useStyles = makeStyles(theme => ({ |
25 | 18 | formContainer: { |
26 | 19 | maxWidth: '30em', |
27 | 20 | marginLeft: 'auto', |
@@ -50,41 +43,13 @@ const useStyles = makeStyles<Theme, UseStyleInput, StyleKeys>(theme => ({ |
50 | 43 | marginLeft: 'auto', |
51 | 44 | marginRight: 'auto', |
52 | 45 | }, |
53 | | - |
54 | | - warningBannerContainer: { |
55 | | - paddingTop: theme.spacing(4), |
56 | | - paddingLeft: theme.spacing(6), |
57 | | - paddingRight: theme.spacing(6), |
58 | | - }, |
59 | | - |
60 | | - warningBanner: ({ status }) => { |
61 | | - let color: string; |
62 | | - let backgroundColor: string; |
63 | | - |
64 | | - if (status === 'invalid') { |
65 | | - color = theme.palette.error.contrastText; |
66 | | - backgroundColor = theme.palette.banner.error; |
67 | | - } else { |
68 | | - color = theme.palette.text.primary; |
69 | | - backgroundColor = theme.palette.background.default; |
70 | | - } |
71 | | - |
72 | | - return { |
73 | | - color, |
74 | | - backgroundColor, |
75 | | - borderRadius: theme.shape.borderRadius, |
76 | | - textAlign: 'center', |
77 | | - paddingTop: theme.spacing(0.5), |
78 | | - paddingBottom: theme.spacing(0.5), |
79 | | - }; |
80 | | - }, |
81 | 46 | })); |
82 | 47 |
|
83 | 48 | export const CoderAuthInputForm = () => { |
84 | 49 | const hookId = useId(); |
| 50 | + const styles = useStyles(); |
85 | 51 | const appConfig = useCoderAppConfig(); |
86 | 52 | const { status, registerNewToken } = useCoderAuth(); |
87 | | - const styles = useStyles({ status }); |
88 | 53 |
|
89 | 54 | const onSubmit = (event: FormEvent<HTMLFormElement>) => { |
90 | 55 | event.preventDefault(); |
@@ -161,13 +126,122 @@ export const CoderAuthInputForm = () => { |
161 | 126 | </fieldset> |
162 | 127 |
|
163 | 128 | {(status === 'invalid' || status === 'authenticating') && ( |
164 | | - <div className={styles.warningBannerContainer}> |
165 | | - <div id={warningBannerId} className={styles.warningBanner}> |
166 | | - {status === 'invalid' && 'Invalid token'} |
167 | | - {status === 'authenticating' && <>Authenticating…</>} |
168 | | - </div> |
169 | | - </div> |
| 129 | + <InvalidStatusNotifier authStatus={status} bannerId={warningBannerId} /> |
170 | 130 | )} |
171 | 131 | </form> |
172 | 132 | ); |
173 | 133 | }; |
| 134 | + |
| 135 | +const useInvalidStatusStyles = makeStyles(theme => ({ |
| 136 | + warningBannerSpacer: { |
| 137 | + paddingTop: theme.spacing(2), |
| 138 | + }, |
| 139 | + |
| 140 | + warningBanner: { |
| 141 | + display: 'flex', |
| 142 | + flexFlow: 'row nowrap', |
| 143 | + alignItems: 'center', |
| 144 | + color: theme.palette.text.primary, |
| 145 | + backgroundColor: theme.palette.background.default, |
| 146 | + borderRadius: theme.shape.borderRadius, |
| 147 | + border: `1.5px solid ${theme.palette.background.default}`, |
| 148 | + padding: 0, |
| 149 | + }, |
| 150 | + |
| 151 | + errorContent: { |
| 152 | + display: 'flex', |
| 153 | + flexFlow: 'row nowrap', |
| 154 | + alignItems: 'center', |
| 155 | + columnGap: theme.spacing(1), |
| 156 | + marginRight: 'auto', |
| 157 | + |
| 158 | + paddingTop: theme.spacing(0.5), |
| 159 | + paddingBottom: theme.spacing(0.5), |
| 160 | + paddingLeft: theme.spacing(2), |
| 161 | + paddingRight: 0, |
| 162 | + }, |
| 163 | + |
| 164 | + icon: { |
| 165 | + fontSize: '16px', |
| 166 | + }, |
| 167 | + |
| 168 | + syncIcon: { |
| 169 | + color: theme.palette.text.primary, |
| 170 | + opacity: 0.6, |
| 171 | + }, |
| 172 | + |
| 173 | + errorIcon: { |
| 174 | + color: theme.palette.error.main, |
| 175 | + fontSize: '16px', |
| 176 | + }, |
| 177 | + |
| 178 | + dismissButton: { |
| 179 | + border: 'none', |
| 180 | + alignSelf: 'stretch', |
| 181 | + padding: `0 ${theme.spacing(1.5)}px 0 ${theme.spacing(2)}px`, |
| 182 | + color: theme.palette.text.primary, |
| 183 | + backgroundColor: 'inherit', |
| 184 | + lineHeight: 1, |
| 185 | + cursor: 'pointer', |
| 186 | + |
| 187 | + '&:hover': { |
| 188 | + backgroundColor: theme.palette.action.hover, |
| 189 | + }, |
| 190 | + }, |
| 191 | + |
| 192 | + '@keyframes spin': { |
| 193 | + '100%': { |
| 194 | + transform: 'rotate(360deg)', |
| 195 | + }, |
| 196 | + }, |
| 197 | +})); |
| 198 | + |
| 199 | +type InvalidStatusProps = Readonly<{ |
| 200 | + authStatus: CoderAuthStatus; |
| 201 | + bannerId: string; |
| 202 | +}>; |
| 203 | + |
| 204 | +function InvalidStatusNotifier({ authStatus, bannerId }: InvalidStatusProps) { |
| 205 | + const [showNotification, setShowNotification] = useState(true); |
| 206 | + const styles = useInvalidStatusStyles(); |
| 207 | + |
| 208 | + if (!showNotification) { |
| 209 | + return null; |
| 210 | + } |
| 211 | + |
| 212 | + return ( |
| 213 | + <div className={styles.warningBannerSpacer}> |
| 214 | + <div id={bannerId} className={styles.warningBanner}> |
| 215 | + <span className={styles.errorContent}> |
| 216 | + {authStatus === 'authenticating' && ( |
| 217 | + <> |
| 218 | + <SyncIcon |
| 219 | + className={`${styles.icon} ${styles.syncIcon}`} |
| 220 | + // Needed to make MUI v4 icons respect sizing values |
| 221 | + fontSize="inherit" |
| 222 | + /> |
| 223 | + Authenticating… |
| 224 | + </> |
| 225 | + )} |
| 226 | + |
| 227 | + {authStatus === 'invalid' && ( |
| 228 | + <> |
| 229 | + <ErrorIcon |
| 230 | + className={`${styles.icon} ${styles.errorIcon}`} |
| 231 | + fontSize="inherit" |
| 232 | + /> |
| 233 | + Invalid token |
| 234 | + </> |
| 235 | + )} |
| 236 | + </span> |
| 237 | + |
| 238 | + <button |
| 239 | + className={styles.dismissButton} |
| 240 | + onClick={() => setShowNotification(false)} |
| 241 | + > |
| 242 | + Dismiss |
| 243 | + </button> |
| 244 | + </div> |
| 245 | + </div> |
| 246 | + ); |
| 247 | +} |
0 commit comments