mirror of https://github.com/grafana/grafana
Migrations: Signup page (#21514)
* Start Angular migration * Add SignupCtrl * Change name signup * Add backend call * Put form in separate file * Add form model * Start using react-hook-forms * Add FormModel to state * Reduxify * Connect nav with Redux * Fix routing and navModel * Fetch state options on mount * Add default values and add button margin * Add errror messages * Fix title * Remove files and cleanup * Add Signup tests * Add boot config assingnAutoOrg and verifyEmailEnabled * Remove onmount call * Remove ctrl and move everything to SignupForm * Make routeParams optional for testing * Remove name if it is empty * Set username * Make function component * Fix subpath issues and add link button * Move redux to SignupPagepull/21387/head
parent
029a6c64b4
commit
0c4dae321c
@ -0,0 +1,18 @@ |
||||
import React from 'react'; |
||||
import { shallow } from 'enzyme'; |
||||
import { SignupForm } from './SignupForm'; |
||||
|
||||
describe('SignupForm', () => { |
||||
describe('With different values for verifyEmail and autoAssignOrg', () => { |
||||
it('should render input fields', () => { |
||||
const wrapper = shallow(<SignupForm verifyEmailEnabled={true} autoAssignOrg={false} />); |
||||
expect(wrapper.exists('Forms.Input[name="orgName"]')); |
||||
expect(wrapper.exists('Forms.Input[name="code"]')); |
||||
}); |
||||
it('should not render input fields', () => { |
||||
const wrapper = shallow(<SignupForm verifyEmailEnabled={false} autoAssignOrg={true} />); |
||||
expect(wrapper.exists('Forms.Input[name="orgName"]')).toBeFalsy(); |
||||
expect(wrapper.exists('Forms.Input[name="code"]')).toBeFalsy(); |
||||
}); |
||||
}); |
||||
}); |
||||
@ -0,0 +1,120 @@ |
||||
import React, { FC } from 'react'; |
||||
import { Forms } from '@grafana/ui'; |
||||
import { css } from 'emotion'; |
||||
|
||||
import { getConfig } from 'app/core/config'; |
||||
import { getBackendSrv } from '@grafana/runtime'; |
||||
|
||||
interface SignupFormModel { |
||||
email: string; |
||||
username?: string; |
||||
password: string; |
||||
orgName: string; |
||||
code?: string; |
||||
name?: string; |
||||
} |
||||
interface Props { |
||||
email?: string; |
||||
orgName?: string; |
||||
username?: string; |
||||
code?: string; |
||||
name?: string; |
||||
verifyEmailEnabled?: boolean; |
||||
autoAssignOrg?: boolean; |
||||
} |
||||
|
||||
const buttonSpacing = css` |
||||
margin-left: 15px; |
||||
`;
|
||||
|
||||
export const SignupForm: FC<Props> = props => { |
||||
const verifyEmailEnabled = props.verifyEmailEnabled; |
||||
const autoAssignOrg = props.autoAssignOrg; |
||||
|
||||
const onSubmit = async (formData: SignupFormModel) => { |
||||
if (formData.name === '') { |
||||
delete formData.name; |
||||
} |
||||
|
||||
const response = await getBackendSrv().post('/api/user/signup/step2', { |
||||
email: formData.email, |
||||
code: formData.code, |
||||
username: formData.email, |
||||
orgName: formData.orgName, |
||||
password: formData.password, |
||||
name: formData.name, |
||||
}); |
||||
|
||||
if (response.code === 'redirect-to-select-org') { |
||||
window.location.href = getConfig().appSubUrl + '/profile/select-org?signup=1'; |
||||
} |
||||
window.location.href = getConfig().appSubUrl + '/'; |
||||
}; |
||||
|
||||
const defaultValues = { |
||||
orgName: props.orgName, |
||||
email: props.email, |
||||
username: props.email, |
||||
code: props.code, |
||||
name: props.name, |
||||
}; |
||||
|
||||
return ( |
||||
<Forms.Form defaultValues={defaultValues} onSubmit={onSubmit}> |
||||
{({ register, errors }) => { |
||||
return ( |
||||
<> |
||||
{verifyEmailEnabled && ( |
||||
<Forms.Field label="Email verification code (sent to your email)"> |
||||
<Forms.Input name="code" size="md" ref={register} placeholder="Code" /> |
||||
</Forms.Field> |
||||
)} |
||||
{!autoAssignOrg && ( |
||||
<Forms.Field label="Org. name"> |
||||
<Forms.Input size="md" name="orgName" placeholder="Org. name" ref={register} /> |
||||
</Forms.Field> |
||||
)} |
||||
<Forms.Field label="Your name"> |
||||
<Forms.Input size="md" name="name" placeholder="(optional)" ref={register} /> |
||||
</Forms.Field> |
||||
<Forms.Field label="Email" invalid={!!errors.email} error={!!errors.email && errors.email.message}> |
||||
<Forms.Input |
||||
size="md" |
||||
name="email" |
||||
type="email" |
||||
placeholder="Email" |
||||
ref={register({ |
||||
required: 'Email is required', |
||||
pattern: { |
||||
value: /^\S+@\S+$/, |
||||
message: 'Email is invalid', |
||||
}, |
||||
})} |
||||
/> |
||||
</Forms.Field> |
||||
<Forms.Field |
||||
label="Password" |
||||
invalid={!!errors.password} |
||||
error={!!errors.password && errors.password.message} |
||||
> |
||||
<Forms.Input |
||||
size="md" |
||||
name="password" |
||||
type="password" |
||||
placeholder="Password" |
||||
ref={register({ required: 'Password is required' })} |
||||
/> |
||||
</Forms.Field> |
||||
|
||||
<Forms.Button type="submit">Submit</Forms.Button> |
||||
<span className={buttonSpacing}> |
||||
<Forms.LinkButton href={getConfig().appSubUrl + '/login'} variant="secondary"> |
||||
Back |
||||
</Forms.LinkButton> |
||||
</span> |
||||
</> |
||||
); |
||||
}} |
||||
</Forms.Form> |
||||
); |
||||
}; |
||||
@ -0,0 +1,51 @@ |
||||
import React, { FC } from 'react'; |
||||
import { SignupForm } from './SignupForm'; |
||||
import Page from 'app/core/components/Page/Page'; |
||||
import { getConfig } from 'app/core/config'; |
||||
import { connect } from 'react-redux'; |
||||
import { hot } from 'react-hot-loader'; |
||||
import { StoreState } from 'app/types'; |
||||
|
||||
const navModel = { |
||||
main: { |
||||
icon: 'gicon gicon-branding', |
||||
text: 'Sign Up', |
||||
subTitle: 'Register your Grafana account', |
||||
breadcrumbs: [{ title: 'Login', url: 'login' }], |
||||
}, |
||||
node: { |
||||
text: '', |
||||
}, |
||||
}; |
||||
|
||||
interface Props { |
||||
email?: string; |
||||
orgName?: string; |
||||
username?: string; |
||||
code?: string; |
||||
name?: string; |
||||
} |
||||
export const SignupPage: FC<Props> = props => { |
||||
return ( |
||||
<Page navModel={navModel}> |
||||
<Page.Contents> |
||||
<h3 className="p-b-1">You're almost there.</h3> |
||||
<div className="p-b-1"> |
||||
We just need a couple of more bits of |
||||
<br /> information to finish creating your account. |
||||
</div> |
||||
<SignupForm |
||||
{...props} |
||||
verifyEmailEnabled={getConfig().verifyEmailEnabled} |
||||
autoAssignOrg={getConfig().autoAssignOrg} |
||||
/> |
||||
</Page.Contents> |
||||
</Page> |
||||
); |
||||
}; |
||||
|
||||
const mapStateToProps = (state: StoreState) => ({ |
||||
...state.location.routeParams, |
||||
}); |
||||
|
||||
export default hot(module)(connect(mapStateToProps)(SignupPage)); |
||||
@ -1,47 +0,0 @@ |
||||
<page-header model="navModel"></page-header> |
||||
|
||||
<div class="page-container page-body"> |
||||
<div class="signup"> |
||||
<h3 class="p-b-1">You're almost there.</h3> |
||||
<div class="p-b-1"> |
||||
We just need a couple of more bits of<br> information to finish creating your account. |
||||
</div> |
||||
<form name="signUpForm" class="login-form gf-form-group"> |
||||
<div class="gf-form" ng-if="verifyEmailEnabled"> |
||||
<span class="gf-form-label width-9"> |
||||
Email code<tip>Email verification code (sent to your email)</tip> |
||||
</span> |
||||
<input type="text" class="gf-form-input max-width-14" ng-model="formModel.code" required></input> |
||||
</div> |
||||
|
||||
<div class="gf-form" ng-if="!autoAssignOrg"> |
||||
<span class="gf-form-label width-9">Org. name</span> |
||||
<input type="text" name="orgName" class="gf-form-input max-width-14" ng-model="formModel.orgName" placeholder="Name your organization"> |
||||
</div> |
||||
|
||||
<div class="gf-form"> |
||||
<span class="gf-form-label width-9">Your name</span> |
||||
<input type="text" name="name" class="gf-form-input max-width-14" ng-model="formModel.name" placeholder="(optional)"> |
||||
</div> |
||||
<div class="gf-form"> |
||||
<span class="gf-form-label width-9">Email</span> |
||||
<input type="text" class="gf-form-input max-width-14" required ng-model="formModel.username" placeholder="Email" autocomplete="off"> |
||||
</div> |
||||
<div class="gf-form"> |
||||
<span class="gf-form-label width-9">Password</span> |
||||
<input type="password" class="gf-form-input max-width-14" required ng-model="formModel.password" id="inputPassword" placeholder="password" autocomplete="off"> |
||||
</div> |
||||
|
||||
<div class="gf-form-button-row p-t-3"> |
||||
<button type="submit" class="btn btn-primary" ng-click="ctrl.submit();" ng-disabled="!signUpForm.$valid"> |
||||
Sign Up |
||||
</button> |
||||
<a href="login" class="btn btn-inverse"> |
||||
Back |
||||
</a> |
||||
</div> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
|
||||
<footer /> |
||||
Loading…
Reference in new issue