web/satellite: Add user account tabs to signup ui
- toggle logic to trigger visual tab change - toggling tabs renders appropriate fields - error handling for professional account inputs - successfully create professional user account Goal is to be able to target users with specific professional focused communication. Change-Id: Iffbeb712dac24ea1a83bb374740e0c1cd2100663
This commit is contained in:
parent
c290e5ac9a
commit
a7ca78a519
@ -205,7 +205,7 @@ export class AuthHttpApi {
|
||||
* @returns id of created user
|
||||
* @throws Error
|
||||
*/
|
||||
public async register(user: { fullName: string; shortName: string; email: string; partner: string; partnerId: string; password: string }, secret: string): Promise<string> {
|
||||
public async register(user: {fullName: string; shortName: string; email: string; partner: string; partnerId: string; password: string; isProfessional: boolean; position: string; companyName: string; employeeCount: string}, secret: string): Promise<string> {
|
||||
const path = `${this.ROOT_PATH}/register`;
|
||||
const body = {
|
||||
secret: secret,
|
||||
@ -215,6 +215,10 @@ export class AuthHttpApi {
|
||||
email: user.email,
|
||||
partner: user.partner ? user.partner : '',
|
||||
partnerId: user.partnerId ? user.partnerId : '',
|
||||
isProfessional: user.isProfessional,
|
||||
position: user.position,
|
||||
companyName: user.companyName,
|
||||
employeeCount: user.employeeCount,
|
||||
};
|
||||
|
||||
const response = await this.http.post(path, JSON.stringify(body));
|
||||
|
@ -19,7 +19,25 @@
|
||||
:style="style.inputStyle"
|
||||
@focus="showPasswordStrength"
|
||||
@blur="hidePasswordStrength"
|
||||
@click="showOptions"
|
||||
:optionsShown="optionsShown"
|
||||
@optionsList="optionsList"
|
||||
/>
|
||||
|
||||
<!-- Shown if there are input choice options -->
|
||||
<InputCaret v-if="optionsList.length > 0" class="headerless-input__caret" />
|
||||
<ul v-click-outside="hideOptions" class="headerless-input__options-wrapper" v-if="optionsShown">
|
||||
<li
|
||||
class="headerless-input__option"
|
||||
@click="chooseOption(option)"
|
||||
v-for="(option, index) in optionsList"
|
||||
:key="index"
|
||||
>
|
||||
{{option}}
|
||||
</li>
|
||||
</ul>
|
||||
<!-- end of option render logic-->
|
||||
|
||||
<!--2 conditions of eye image (crossed or not) -->
|
||||
<PasswordHiddenIcon
|
||||
class="input-wrap__image"
|
||||
@ -38,6 +56,7 @@
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
|
||||
import InputCaret from '@/../static/images/common/caret.svg';
|
||||
import PasswordHiddenIcon from '@/../static/images/common/passwordHidden.svg';
|
||||
import PasswordShownIcon from '@/../static/images/common/passwordShown.svg';
|
||||
import ErrorIcon from '@/../static/images/register/ErrorInfo.svg';
|
||||
@ -45,6 +64,7 @@ import ErrorIcon from '@/../static/images/register/ErrorInfo.svg';
|
||||
// Custom input component for login page
|
||||
@Component({
|
||||
components: {
|
||||
InputCaret,
|
||||
ErrorIcon,
|
||||
PasswordHiddenIcon,
|
||||
PasswordShownIcon,
|
||||
@ -73,6 +93,12 @@ export default class HeaderlessInput extends Vue {
|
||||
protected readonly error: string;
|
||||
@Prop({default: Number.MAX_SAFE_INTEGER})
|
||||
protected readonly maxSymbols: number;
|
||||
@Prop({default: []})
|
||||
protected readonly optionsList: [string];
|
||||
@Prop({default: false})
|
||||
protected optionsShown: boolean;
|
||||
@Prop({default: false})
|
||||
protected inputClicked: boolean;
|
||||
|
||||
@Prop({default: false})
|
||||
private readonly isWhite: boolean;
|
||||
@ -120,6 +146,36 @@ export default class HeaderlessInput extends Vue {
|
||||
this.type = this.isPasswordShown ? this.textType : this.passwordType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Chose a dropdown option as the input value.
|
||||
*/
|
||||
public chooseOption(option: string): void {
|
||||
this.value = option;
|
||||
this.$emit('setData', this.value);
|
||||
this.optionsShown = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show dropdown options when the input is clicked, if they exist.
|
||||
*/
|
||||
public showOptions(): void {
|
||||
if (this.optionsList.length > 0) {
|
||||
this.optionsShown = true;
|
||||
this.inputClicked = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the dropdown options from view when there is a click outside of the dropdown.
|
||||
*/
|
||||
public hideOptions(): void {
|
||||
if (this.optionsList.length > 0 && !this.inputClicked && this.optionsShown) {
|
||||
this.optionsShown = false;
|
||||
this.inputClicked = false;
|
||||
}
|
||||
this.inputClicked = false;
|
||||
}
|
||||
|
||||
public get isLabelShown(): boolean {
|
||||
return !!(!this.error && this.label);
|
||||
}
|
||||
@ -170,6 +226,72 @@ export default class HeaderlessInput extends Vue {
|
||||
fill: #2683ff !important;
|
||||
}
|
||||
}
|
||||
|
||||
.headerless-input {
|
||||
font-size: 16px;
|
||||
line-height: 21px;
|
||||
resize: none;
|
||||
height: 46px;
|
||||
padding: 0 30px 0 0;
|
||||
width: calc(100% - 30px) !important;
|
||||
text-indent: 20px;
|
||||
border: 1px solid rgba(56, 75, 101, 0.4);
|
||||
border-radius: 6px;
|
||||
|
||||
&__caret {
|
||||
position: absolute;
|
||||
right: 28px;
|
||||
bottom: 18px;
|
||||
}
|
||||
|
||||
&__options-wrapper {
|
||||
border: 1px solid rgba(56, 75, 101, 0.4);
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: 89px;
|
||||
padding: 0;
|
||||
background: #fff;
|
||||
z-index: 21;
|
||||
border-radius: 6px;
|
||||
list-style: none;
|
||||
border-top-right-radius: 0;
|
||||
border-top-left-radius: 0;
|
||||
border-top: none;
|
||||
height: 176px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&__option {
|
||||
cursor: pointer;
|
||||
padding: 20px 22px;
|
||||
|
||||
&:hover {
|
||||
background: #2582ff;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.headerless-input::placeholder {
|
||||
color: #384b65;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
&:focus-within {
|
||||
|
||||
.headerless-input {
|
||||
position: relative;
|
||||
z-index: 22;
|
||||
|
||||
&__options-wrapper {
|
||||
border-top: 3px solid #145ecc;
|
||||
}
|
||||
|
||||
&__caret {
|
||||
z-index: 23;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.label-container {
|
||||
@ -199,23 +321,6 @@ export default class HeaderlessInput extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
.headerless-input {
|
||||
font-size: 16px;
|
||||
line-height: 21px;
|
||||
resize: none;
|
||||
height: 46px;
|
||||
padding: 0 30px 0 0;
|
||||
width: calc(100% - 30px) !important;
|
||||
text-indent: 20px;
|
||||
border: 1px solid rgba(56, 75, 101, 0.4);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.headerless-input::placeholder {
|
||||
color: #384b65;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.inputError::placeholder {
|
||||
color: #eb5757;
|
||||
opacity: 0.4;
|
||||
|
@ -34,6 +34,10 @@ export class User {
|
||||
public partnerId: string = '',
|
||||
public password: string = '',
|
||||
public projectLimit: number = 0,
|
||||
public isProfessional: boolean = false,
|
||||
public position: string = '',
|
||||
public companyName: string = '',
|
||||
public employeeCount: string = '',
|
||||
) {}
|
||||
|
||||
public getFullName(): string {
|
||||
|
@ -55,8 +55,12 @@ export default class RegisterArea extends Vue {
|
||||
private emailError: string = '';
|
||||
private passwordError: string = '';
|
||||
private repeatedPasswordError: string = '';
|
||||
private companyNameError: string = '';
|
||||
private employeeCountError: string = '';
|
||||
private positionError: string = '';
|
||||
private isTermsAcceptedError: boolean = false;
|
||||
private isLoading: boolean = false;
|
||||
private isProfessional: boolean = false;
|
||||
|
||||
// Only for beta sats (like US2).
|
||||
private areBetaTermsAcceptedError: boolean = false;
|
||||
@ -68,6 +72,10 @@ export default class RegisterArea extends Vue {
|
||||
// tardigrade logic
|
||||
public isDropdownShown: boolean = false;
|
||||
|
||||
// Employee Count dropdown options
|
||||
public employeeCountOptions = ['1-50', '51-1000', '1001+'];
|
||||
public optionsShown = false;
|
||||
|
||||
/**
|
||||
* Lifecycle hook before vue instance is created.
|
||||
* Initializes google tag manager (Tardigrade).
|
||||
@ -161,7 +169,6 @@ export default class RegisterArea extends Vue {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await this.createUser();
|
||||
|
||||
this.isLoading = false;
|
||||
@ -221,6 +228,37 @@ export default class RegisterArea extends Vue {
|
||||
return this.$store.state.appStateModule.isBetaSatellite;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets user's company name field from value string.
|
||||
*/
|
||||
public setCompanyName(value: string): void {
|
||||
this.user.companyName = value.trim();
|
||||
this.companyNameError = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets user's company size field from value string.
|
||||
*/
|
||||
public setEmployeeCount(value: string): void {
|
||||
this.user.employeeCount = value;
|
||||
this.employeeCountError = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets user's position field from value string.
|
||||
*/
|
||||
public setPosition(value: string): void {
|
||||
this.user.position = value.trim();
|
||||
this.positionError = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* toggle user account type
|
||||
*/
|
||||
public toggleAccountType(value: boolean): void {
|
||||
this.isProfessional = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates input values to satisfy expected rules.
|
||||
*/
|
||||
@ -242,6 +280,25 @@ export default class RegisterArea extends Vue {
|
||||
isNoErrors = false;
|
||||
}
|
||||
|
||||
if (this.isProfessional) {
|
||||
|
||||
if (!this.user.companyName.trim()) {
|
||||
this.companyNameError = 'No Company Name filled in';
|
||||
isNoErrors = false;
|
||||
}
|
||||
|
||||
if (!this.user.position.trim()) {
|
||||
this.positionError = 'No Position filled in';
|
||||
isNoErrors = false;
|
||||
}
|
||||
|
||||
if (!this.user.employeeCount.trim()) {
|
||||
this.employeeCountError = 'No Company Size filled in';
|
||||
isNoErrors = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (this.repeatedPassword !== this.password) {
|
||||
this.repeatedPasswordError = 'Password doesn\'t match';
|
||||
isNoErrors = false;
|
||||
@ -265,14 +322,23 @@ export default class RegisterArea extends Vue {
|
||||
* Creates user and toggles successful registration area visibility.
|
||||
*/
|
||||
private async createUser(): Promise<void> {
|
||||
this.user.isProfessional = this.isProfessional;
|
||||
try {
|
||||
this.userId = await this.auth.register(this.user, this.secret);
|
||||
|
||||
LocalData.setUserId(this.userId);
|
||||
|
||||
this.$segment.identify(this.userId, {
|
||||
email: this.$store.getters.user.email,
|
||||
});
|
||||
if (this.user.isProfessional) {
|
||||
this.$segment.identify(this.userId, {
|
||||
email: this.$store.getters.user.email,
|
||||
position: this.$store.getters.user.position,
|
||||
company_name: this.$store.getters.user.companyName,
|
||||
employee_count: this.$store.getters.user.employeeCount,
|
||||
});
|
||||
} else {
|
||||
this.$segment.identify(this.userId, {
|
||||
email: this.$store.getters.user.email,
|
||||
});
|
||||
}
|
||||
|
||||
const verificationPageURL: string = MetaUtils.getMetaContent('verification-page-url');
|
||||
if (verificationPageURL) {
|
||||
@ -285,7 +351,6 @@ export default class RegisterArea extends Vue {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_SUCCESSFUL_REGISTRATION);
|
||||
} catch (error) {
|
||||
await this.$notify.error(error.message);
|
||||
|
3
web/satellite/static/images/common/caret.svg
Normal file
3
web/satellite/static/images/common/caret.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="15" height="7" viewBox="0 0 15 7" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.5 7L0.138785 0.25L14.8612 0.250001L7.5 7Z" fill="#384761"/>
|
||||
</svg>
|
After Width: | Height: | Size: 173 B |
@ -11,6 +11,8 @@ exports[`ApiKeysCreationPopup renders correctly 1`] = `
|
||||
</div> <input placeholder="Enter API Key Name" type="text" class="headerless-input" style="width: 100%; height: 48px;">
|
||||
<!---->
|
||||
<!---->
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
<div class="next-button container" style="width: 128px; height: 48px;"><span class="label">Next ></span></div>
|
||||
<div class="new-api-key__close-cross-container"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
|
@ -9,6 +9,8 @@ exports[`HeaderlessInput.vue renders correctly with default props 1`] = `
|
||||
</div> <input placeholder="default" type="text" class="headerless-input" style="width: 100%; height: 48px;">
|
||||
<!---->
|
||||
<!---->
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
`;
|
||||
|
||||
@ -18,7 +20,9 @@ exports[`HeaderlessInput.vue renders correctly with isPassword prop 1`] = `
|
||||
<!---->
|
||||
<!---->
|
||||
<!---->
|
||||
</div> <input placeholder="default" type="password" class="headerless-input password" style="width: 100%; height: 48px;"> <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" class="input-wrap__image">
|
||||
</div> <input placeholder="default" type="password" class="headerless-input password" style="width: 100%; height: 48px;">
|
||||
<!---->
|
||||
<!----> <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" class="input-wrap__image">
|
||||
<path d="M10 4C4.70642 4 1 10 1 10C1 10 3.6999 16 10 16C16.3527 16 19 10 19 10C19 10 15.3472 4 10 4ZM10 13.8176C7.93537 13.8176 6.2946 12.1271 6.2946 10C6.2946 7.87285 7.93537 6.18239 10 6.18239C12.0646 6.18239 13.7054 7.87285 13.7054 10C13.7054 12.1271 12.0646 13.8176 10 13.8176Z" fill="#AFB7C1" class="input-wrap__image__path"></path>
|
||||
<path d="M11.6116 9.96328C11.6116 10.8473 10.8956 11.5633 10.0116 11.5633C9.12763 11.5633 8.41162 10.8473 8.41162 9.96328C8.41162 9.07929 9.12763 8.36328 10.0116 8.36328C10.8956 8.36328 11.6116 9.07929 11.6116 9.96328Z" fill="#AFB7C1"></path>
|
||||
</svg>
|
||||
@ -35,5 +39,7 @@ exports[`HeaderlessInput.vue renders correctly with size props 1`] = `
|
||||
</div> <input placeholder="test" type="text" class="headerless-input" style="width: 30px; height: 20px;">
|
||||
<!---->
|
||||
<!---->
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
`;
|
||||
|
Loading…
Reference in New Issue
Block a user