web, satellite: allow registering business accounts to ask for contact from sales team

Full prefix: web/satellite, satellite/{console, analytics, satellitedb}

- checkbox added to register view - business tab
- user being saved with new column
- add sales contact choice to Segment calls
- ui fix added to employee count dropdown

Change-Id: Ib976872463b88874ea9714db635d58c79cdbe3a1
This commit is contained in:
Malcolm Bouzi 2021-04-27 20:40:03 +02:00
parent 2cf10a7bf4
commit 136af8e630
11 changed files with 122 additions and 79 deletions

View File

@ -79,14 +79,15 @@ const (
// TrackCreateUserFields contains input data for tracking a create user event.
type TrackCreateUserFields struct {
ID uuid.UUID
AnonymousID string
FullName string
Email string
Type UserType
EmployeeCount string
CompanyName string
JobTitle string
ID uuid.UUID
AnonymousID string
FullName string
Email string
Type UserType
EmployeeCount string
CompanyName string
JobTitle string
HaveSalesContact bool
}
func (service *Service) enqueueMessage(message segment.Message) {
@ -122,6 +123,7 @@ func (service *Service) TrackCreateUser(fields TrackCreateUserFields) {
props.Set("company_size", fields.EmployeeCount)
props.Set("company_name", fields.CompanyName)
props.Set("job_title", fields.JobTitle)
props.Set("have_sales_contact", fields.HaveSalesContact)
}
service.enqueueMessage(segment.Track{

View File

@ -112,18 +112,19 @@ func (a *Auth) Register(w http.ResponseWriter, r *http.Request) {
defer mon.Task()(&ctx)(&err)
var registerData struct {
FullName string `json:"fullName"`
ShortName string `json:"shortName"`
Email string `json:"email"`
Partner string `json:"partner"`
PartnerID string `json:"partnerId"`
Password string `json:"password"`
SecretInput string `json:"secret"`
ReferrerUserID string `json:"referrerUserId"`
IsProfessional bool `json:"isProfessional"`
Position string `json:"position"`
CompanyName string `json:"companyName"`
EmployeeCount string `json:"employeeCount"`
FullName string `json:"fullName"`
ShortName string `json:"shortName"`
Email string `json:"email"`
Partner string `json:"partner"`
PartnerID string `json:"partnerId"`
Password string `json:"password"`
SecretInput string `json:"secret"`
ReferrerUserID string `json:"referrerUserId"`
IsProfessional bool `json:"isProfessional"`
Position string `json:"position"`
CompanyName string `json:"companyName"`
EmployeeCount string `json:"employeeCount"`
HaveSalesContact bool `json:"haveSalesContact"`
}
err = json.NewDecoder(r.Body).Decode(&registerData)
@ -149,15 +150,16 @@ func (a *Auth) Register(w http.ResponseWriter, r *http.Request) {
user, err := a.service.CreateUser(ctx,
console.CreateUser{
FullName: registerData.FullName,
ShortName: registerData.ShortName,
Email: registerData.Email,
PartnerID: registerData.PartnerID,
Password: registerData.Password,
IsProfessional: registerData.IsProfessional,
Position: registerData.Position,
CompanyName: registerData.CompanyName,
EmployeeCount: registerData.EmployeeCount,
FullName: registerData.FullName,
ShortName: registerData.ShortName,
Email: registerData.Email,
PartnerID: registerData.PartnerID,
Password: registerData.Password,
IsProfessional: registerData.IsProfessional,
Position: registerData.Position,
CompanyName: registerData.CompanyName,
EmployeeCount: registerData.EmployeeCount,
HaveSalesContact: registerData.HaveSalesContact,
},
secret,
)
@ -178,6 +180,7 @@ func (a *Auth) Register(w http.ResponseWriter, r *http.Request) {
trackCreateUserFields.EmployeeCount = user.EmployeeCount
trackCreateUserFields.CompanyName = user.CompanyName
trackCreateUserFields.JobTitle = user.Position
trackCreateUserFields.HaveSalesContact = user.HaveSalesContact
}
a.analytics.TrackCreateUser(trackCreateUserFields)
@ -250,16 +253,17 @@ func (a *Auth) GetAccount(w http.ResponseWriter, r *http.Request) {
defer mon.Task()(&ctx)(&err)
var user struct {
ID uuid.UUID `json:"id"`
FullName string `json:"fullName"`
ShortName string `json:"shortName"`
Email string `json:"email"`
PartnerID uuid.UUID `json:"partnerId"`
ProjectLimit int `json:"projectLimit"`
IsProfessional bool `json:"isProfessional"`
Position string `json:"position"`
CompanyName string `json:"companyName"`
EmployeeCount string `json:"employeeCount"`
ID uuid.UUID `json:"id"`
FullName string `json:"fullName"`
ShortName string `json:"shortName"`
Email string `json:"email"`
PartnerID uuid.UUID `json:"partnerId"`
ProjectLimit int `json:"projectLimit"`
IsProfessional bool `json:"isProfessional"`
Position string `json:"position"`
CompanyName string `json:"companyName"`
EmployeeCount string `json:"employeeCount"`
HaveSalesContact bool `json:"haveSalesContact"`
}
auth, err := console.GetAuth(ctx)
@ -278,6 +282,7 @@ func (a *Auth) GetAccount(w http.ResponseWriter, r *http.Request) {
user.CompanyName = auth.User.CompanyName
user.Position = auth.User.Position
user.EmployeeCount = auth.User.EmployeeCount
user.HaveSalesContact = auth.User.HaveSalesContact
w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(&user)

View File

@ -561,17 +561,19 @@ func (s *Service) CreateUser(ctx context.Context, user CreateUser, tokenSecret R
}
newUser := &User{
ID: userID,
Email: user.Email,
FullName: user.FullName,
ShortName: user.ShortName,
PasswordHash: hash,
Status: Inactive,
IsProfessional: user.IsProfessional,
Position: user.Position,
CompanyName: user.CompanyName,
EmployeeCount: user.EmployeeCount,
ID: userID,
Email: user.Email,
FullName: user.FullName,
ShortName: user.ShortName,
PasswordHash: hash,
Status: Inactive,
IsProfessional: user.IsProfessional,
Position: user.Position,
CompanyName: user.CompanyName,
EmployeeCount: user.EmployeeCount,
HaveSalesContact: user.HaveSalesContact,
}
if user.PartnerID != "" {
newUser.PartnerID, err = uuid.FromString(user.PartnerID)
if err != nil {

View File

@ -49,16 +49,17 @@ func (user *UserInfo) IsValid() error {
// CreateUser struct holds info for User creation.
type CreateUser struct {
FullName string `json:"fullName"`
ShortName string `json:"shortName"`
Email string `json:"email"`
PartnerID string `json:"partnerId"`
Password string `json:"password"`
IsProfessional bool `json:"isProfessional"`
Position string `json:"position"`
CompanyName string `json:"companyName"`
WorkingOn string `json:"workingOn"`
EmployeeCount string `json:"employeeCount"`
FullName string `json:"fullName"`
ShortName string `json:"shortName"`
Email string `json:"email"`
PartnerID string `json:"partnerId"`
Password string `json:"password"`
IsProfessional bool `json:"isProfessional"`
Position string `json:"position"`
CompanyName string `json:"companyName"`
WorkingOn string `json:"workingOn"`
EmployeeCount string `json:"employeeCount"`
HaveSalesContact bool `json:"haveSalesContact"`
}
// IsValid checks CreateUser validity and returns error describing whats wrong.
@ -117,4 +118,6 @@ type User struct {
CompanySize int `json:"companySize"`
WorkingOn string `json:"workingOn"`
EmployeeCount string `json:"employeeCount"`
HaveSalesContact bool `json:"haveSalesContact"`
}

View File

@ -68,6 +68,7 @@ func (users *users) Insert(ctx context.Context, user *console.User) (_ *console.
optional.CompanyName = dbx.User_CompanyName(user.CompanyName)
optional.WorkingOn = dbx.User_WorkingOn(user.WorkingOn)
optional.EmployeeCount = dbx.User_EmployeeCount(user.EmployeeCount)
optional.HaveSalesContact = dbx.User_HaveSalesContact(user.HaveSalesContact)
}
createdUser, err := users.db.Create_User(ctx,
@ -150,14 +151,15 @@ func userFromDBX(ctx context.Context, user *dbx.User) (_ *console.User, err erro
}
result := console.User{
ID: id,
FullName: user.FullName,
Email: user.Email,
PasswordHash: user.PasswordHash,
Status: console.UserStatus(user.Status),
CreatedAt: user.CreatedAt,
ProjectLimit: user.ProjectLimit,
IsProfessional: user.IsProfessional,
ID: id,
FullName: user.FullName,
Email: user.Email,
PasswordHash: user.PasswordHash,
Status: console.UserStatus(user.Status),
CreatedAt: user.CreatedAt,
ProjectLimit: user.ProjectLimit,
IsProfessional: user.IsProfessional,
HaveSalesContact: user.HaveSalesContact,
}
if user.PartnerId != nil {

View File

@ -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; isProfessional: boolean; position: string; companyName: string; employeeCount: 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; haveSalesContact: boolean }, secret: string): Promise<string> {
const path = `${this.ROOT_PATH}/register`;
const body = {
secret: secret,
@ -219,8 +219,8 @@ export class AuthHttpApi {
position: user.position,
companyName: user.companyName,
employeeCount: user.employeeCount,
haveSalesContact: user.haveSalesContact,
};
const response = await this.http.post(path, JSON.stringify(body));
if (!response.ok) {
switch (response.status) {

View File

@ -252,8 +252,8 @@ export default class HeaderlessInput extends Vue {
&__options-wrapper {
border: 1px solid rgba(56, 75, 101, 0.4);
position: absolute;
width: calc(100% - 4px);
top: 86px;
width: calc(100% - 5px);
top: 70px;
padding: 0;
background: #fff;
z-index: 21;

View File

@ -38,6 +38,7 @@ export class User {
public position: string = '',
public companyName: string = '',
public employeeCount: string = '',
public haveSalesContact: boolean = false,
) {}
public getFullName(): string {

View File

@ -65,6 +65,7 @@ export default class RegisterArea extends Vue {
private isTermsAcceptedError: boolean = false;
private isLoading: boolean = false;
private isProfessional: boolean = false;
private haveSalesContact: boolean = false;
private readonly auth: AuthHttpApi = new AuthHttpApi();
@ -328,6 +329,8 @@ export default class RegisterArea extends Vue {
*/
private async createUser(): Promise<void> {
this.user.isProfessional = this.isProfessional;
this.user.haveSalesContact = this.haveSalesContact;
try {
this.userId = await this.auth.register(this.user, this.secret);
LocalData.setUserId(this.userId);

View File

@ -137,17 +137,28 @@
/>
</div>
<AddCouponCodeInput v-if="couponCodeUIEnabled" />
<div class="register-area__content-area__container__terms-area">
<div v-if="isProfessional" class="register-area__content-area__container__checkbox-area">
<label class="container">
<input type="checkbox" v-model="haveSalesContact">
<span class="checkmark"></span>
</label>
<label class="register-area__content-area__container__checkbox-area__msg-box" for="terms">
<p class="register-area__content-area__container__checkbox-area__msg-box__msg">
Please have the Sales Team contact me
</p>
</label>
</div>
<div class="register-area__content-area__container__checkbox-area">
<label class="container">
<input id="terms" type="checkbox" v-model="isTermsAccepted">
<span class="checkmark" :class="{'error': isTermsAcceptedError}"></span>
</label>
<label class="register-area__content-area__container__terms-area__msg-box" for="terms">
<p class="register-area__content-area__container__terms-area__msg-box__msg">
<label class="register-area__content-area__container__checkbox-area__msg-box" for="terms">
<p class="register-area__content-area__container__checkbox-area__msg-box__msg">
I agree to the
<a class="register-area__content-area__container__terms-area__msg-box__msg__link" href="https://storj.io/terms-of-service/" target="_blank" rel="noopener">Terms of Service</a>
<a class="register-area__content-area__container__checkbox-area__msg-box__msg__link" href="https://storj.io/terms-of-service/" target="_blank" rel="noopener">Terms of Service</a>
and
<a class="register-area__content-area__container__terms-area__msg-box__msg__link" href="https://storj.io/privacy-policy/" target="_blank" rel="noopener">Privacy Policy</a>
<a class="register-area__content-area__container__checkbox-area__msg-box__msg__link" href="https://storj.io/privacy-policy/" target="_blank" rel="noopener">Privacy Policy</a>
</p>
</label>
</div>

View File

@ -118,6 +118,8 @@ h1 {
border-top-right-radius: 20px;
border-bottom-right-radius: 20px;
border-left: none;
position: relative;
right: 1px;
}
&__personal,
@ -145,7 +147,7 @@ h1 {
padding: 60px 80px;
background-color: #fff;
border-radius: 6px;
min-height: 675px;
min-height: 655px;
&__title-area {
display: flex;
@ -169,7 +171,7 @@ h1 {
}
}
&__terms-area {
&__checkbox-area {
display: flex;
align-items: center;
width: 100%;
@ -181,6 +183,8 @@ h1 {
color: #354049;
&__msg {
position: relative;
top: 4px;
&__link {
margin: 0 4px;
@ -219,7 +223,7 @@ h1 {
}
&__container.professional-container {
min-height: 901px;
min-height: 991px;
}
&__footer {
@ -377,6 +381,16 @@ h1 {
}
}
@media screen and (max-width: 414px) {
.register-area {
&__logo-wrapper {
margin-top: 30px;
}
}
}
@media screen and (max-width: 414px) {
.register-area {