mirror of https://github.com/grafana/grafana
ForgottenPassword: Move view to login screen (#25366)
* Add forgot password to login screen * Add ForgottenPassword component * Add spacing * Generate new emails and handle resetCode * Fix animation and small UX issues * Extract LoginLayout and add route for reset mail * Reset email template * Add ChangePasswordPage * Remove resetCode * Move style into variable * Fix strict nullpull/25772/head
parent
1bde4de827
commit
f5de4f1fb1
@ -0,0 +1,16 @@ |
||||
import React, { FC } from 'react'; |
||||
import { LoginLayout, InnerBox } from '../Login/LoginLayout'; |
||||
import { ChangePassword } from './ChangePassword'; |
||||
import LoginCtrl from '../Login/LoginCtrl'; |
||||
|
||||
export const ChangePasswordPage: FC = () => { |
||||
return ( |
||||
<LoginLayout> |
||||
<InnerBox> |
||||
<LoginCtrl>{({ changePassword }) => <ChangePassword onSubmit={changePassword} />}</LoginCtrl> |
||||
</InnerBox> |
||||
</LoginLayout> |
||||
); |
||||
}; |
||||
|
||||
export default ChangePasswordPage; |
@ -0,0 +1,66 @@ |
||||
import React, { FC, useState } from 'react'; |
||||
import { Form, Field, Input, Button, Legend, Container, useStyles, HorizontalGroup, LinkButton } from '@grafana/ui'; |
||||
import { getBackendSrv } from '@grafana/runtime'; |
||||
import { css } from 'emotion'; |
||||
import { GrafanaTheme } from '@grafana/data'; |
||||
|
||||
interface EmailDTO { |
||||
userOrEmail: string; |
||||
} |
||||
|
||||
const paragraphStyles = (theme: GrafanaTheme) => css` |
||||
color: ${theme.colors.formDescription}; |
||||
font-size: ${theme.typography.size.sm}; |
||||
font-weight: ${theme.typography.weight.regular}; |
||||
margin-top: ${theme.spacing.sm}; |
||||
display: block; |
||||
`;
|
||||
|
||||
export const ForgottenPassword: FC = () => { |
||||
const [emailSent, setEmailSent] = useState(false); |
||||
const styles = useStyles(paragraphStyles); |
||||
|
||||
const sendEmail = async (formModel: EmailDTO) => { |
||||
const res = await getBackendSrv().post('/api/user/password/send-reset-email', formModel); |
||||
if (res) { |
||||
setEmailSent(true); |
||||
} |
||||
}; |
||||
|
||||
if (emailSent) { |
||||
return ( |
||||
<div> |
||||
<p>An email with a reset link has been sent to the email address. You should receive it shortly.</p> |
||||
<Container margin="md" /> |
||||
<LinkButton variant="primary" href="/login"> |
||||
Back to login |
||||
</LinkButton> |
||||
</div> |
||||
); |
||||
} |
||||
return ( |
||||
<Form onSubmit={sendEmail}> |
||||
{({ register, errors }) => ( |
||||
<> |
||||
<Legend>Reset password</Legend> |
||||
<Field |
||||
label="User" |
||||
description="Enter your informaton to get a reset link sent to you" |
||||
invalid={!!errors.userOrEmail} |
||||
error={errors?.userOrEmail?.message} |
||||
> |
||||
<Input placeholder="Email or username" name="userOrEmail" ref={register({ required: true })} /> |
||||
</Field> |
||||
<HorizontalGroup> |
||||
<Button>Send reset email</Button> |
||||
<LinkButton variant="link" href="/login"> |
||||
Back to login |
||||
</LinkButton> |
||||
</HorizontalGroup> |
||||
|
||||
<p className={styles}>Did you forget your username or email? Contact your Grafana administrator.</p> |
||||
</> |
||||
)} |
||||
</Form> |
||||
); |
||||
}; |
@ -0,0 +1,14 @@ |
||||
import React, { FC } from 'react'; |
||||
|
||||
import { LoginLayout, InnerBox } from '../Login/LoginLayout'; |
||||
import { ForgottenPassword } from './ForgottenPassword'; |
||||
|
||||
export const SendResetMailPage: FC = () => ( |
||||
<LoginLayout> |
||||
<InnerBox> |
||||
<ForgottenPassword /> |
||||
</InnerBox> |
||||
</LoginLayout> |
||||
); |
||||
|
||||
export default SendResetMailPage; |
@ -0,0 +1,123 @@ |
||||
import React, { FC } from 'react'; |
||||
import { cx, css, keyframes } from 'emotion'; |
||||
import { useStyles } from '@grafana/ui'; |
||||
import { Branding } from '../Branding/Branding'; |
||||
import { GrafanaTheme } from '@grafana/data'; |
||||
import { Footer } from '../Footer/Footer'; |
||||
|
||||
interface InnerBoxProps { |
||||
enterAnimation?: boolean; |
||||
} |
||||
export const InnerBox: FC<InnerBoxProps> = ({ children, enterAnimation = true }) => { |
||||
const loginStyles = useStyles(getLoginStyles); |
||||
return <div className={cx(loginStyles.loginInnerBox, enterAnimation && loginStyles.enterAnimation)}>{children}</div>; |
||||
}; |
||||
|
||||
export const LoginLayout: FC = ({ children }) => { |
||||
const loginStyles = useStyles(getLoginStyles); |
||||
return ( |
||||
<Branding.LoginBackground className={loginStyles.container}> |
||||
<div className={cx(loginStyles.loginContent, Branding.LoginBoxBackground())}> |
||||
<div className={loginStyles.loginLogoWrapper}> |
||||
<Branding.LoginLogo className={loginStyles.loginLogo} /> |
||||
<div className={loginStyles.titleWrapper}> |
||||
<h1 className={loginStyles.mainTitle}>{Branding.LoginTitle}</h1> |
||||
<h3 className={loginStyles.subTitle}>{Branding.GetLoginSubTitle()}</h3> |
||||
</div> |
||||
</div> |
||||
<div className={loginStyles.loginOuterBox}>{children}</div> |
||||
</div> |
||||
<Footer /> |
||||
</Branding.LoginBackground> |
||||
); |
||||
}; |
||||
|
||||
const flyInAnimation = keyframes` |
||||
from{ |
||||
opacity: 0; |
||||
transform: translate(-60px, 0px); |
||||
} |
||||
|
||||
to{ |
||||
opacity: 1; |
||||
transform: translate(0px, 0px); |
||||
}`;
|
||||
|
||||
export const getLoginStyles = (theme: GrafanaTheme) => { |
||||
return { |
||||
container: css` |
||||
min-height: 100vh; |
||||
background-position: center; |
||||
background-repeat: no-repeat; |
||||
min-width: 100%; |
||||
margin-left: 0; |
||||
background-color: $black; |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
`,
|
||||
submitButton: css` |
||||
justify-content: center; |
||||
width: 100%; |
||||
`,
|
||||
loginLogo: css` |
||||
width: 100%; |
||||
max-width: 100px; |
||||
margin-bottom: 15px; |
||||
`,
|
||||
loginLogoWrapper: css` |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
flex-direction: column; |
||||
padding: ${theme.spacing.lg}; |
||||
`,
|
||||
titleWrapper: css` |
||||
text-align: center; |
||||
`,
|
||||
mainTitle: css` |
||||
font-size: '32px'; |
||||
`,
|
||||
subTitle: css` |
||||
font-size: ${theme.typography.size.md}; |
||||
color: ${theme.colors.textSemiWeak}; |
||||
`,
|
||||
loginContent: css` |
||||
max-width: 550px; |
||||
width: 100%; |
||||
display: flex; |
||||
align-items: stretch; |
||||
flex-direction: column; |
||||
position: relative; |
||||
justify-content: center; |
||||
z-index: 1; |
||||
min-height: 320px; |
||||
border-radius: 3px; |
||||
padding: 20px 0; |
||||
`,
|
||||
loginOuterBox: css` |
||||
display: flex; |
||||
overflow-y: hidden; |
||||
align-items: center; |
||||
justify-content: center; |
||||
`,
|
||||
loginInnerBox: css` |
||||
padding: ${theme.spacing.xl}; |
||||
@media (max-width: 320px) { |
||||
padding: ${theme.spacing.lg}; |
||||
} |
||||
display: flex; |
||||
flex-direction: column; |
||||
align-items: center; |
||||
justify-content: center; |
||||
flex-grow: 1; |
||||
max-width: 415px; |
||||
width: 100%; |
||||
transform: translate(0px, 0px); |
||||
transition: 0.25s ease; |
||||
`,
|
||||
enterAnimation: css` |
||||
animation: ${flyInAnimation} ease-out 0.2s; |
||||
`,
|
||||
}; |
||||
}; |
@ -1,13 +1,25 @@ |
||||
import React, { FC } from 'react'; |
||||
import { LinkButton, HorizontalGroup } from '@grafana/ui'; |
||||
import { LinkButton, VerticalGroup } from '@grafana/ui'; |
||||
import { css } from 'emotion'; |
||||
|
||||
export const UserSignup: FC<{}> = () => { |
||||
return ( |
||||
<HorizontalGroup justify="flex-start"> |
||||
<LinkButton href="signup" variant="secondary"> |
||||
<VerticalGroup |
||||
className={css` |
||||
margin-top: 8px; |
||||
`}
|
||||
> |
||||
<span>New to Grafana?</span> |
||||
<LinkButton |
||||
className={css` |
||||
width: 100%; |
||||
justify-content: center; |
||||
`}
|
||||
href="signup" |
||||
variant="secondary" |
||||
> |
||||
Sign Up |
||||
</LinkButton> |
||||
<span>New to Grafana?</span> |
||||
</HorizontalGroup> |
||||
</VerticalGroup> |
||||
); |
||||
}; |
||||
|
Loading…
Reference in new issue