aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2019-05-29 11:03:01 +0200
committerChocobozzz <me@florianbigard.com>2019-05-29 11:19:54 +0200
commit1d5342abc43df02cf0bd69b1e865c0f179182eef (patch)
treef008b43ca8189ee5856e39e5b9d4800bf37f9575
parente590b4a512617bbf63595b684386f68abea7d8b8 (diff)
downloadPeerTube-1d5342abc43df02cf0bd69b1e865c0f179182eef.tar.gz
PeerTube-1d5342abc43df02cf0bd69b1e865c0f179182eef.tar.zst
PeerTube-1d5342abc43df02cf0bd69b1e865c0f179182eef.zip
Multi step registration
-rw-r--r--client/src/app/shared/forms/peertube-checkbox.component.scss3
-rw-r--r--client/src/app/shared/users/user.service.ts3
-rw-r--r--client/src/app/signup/custom-stepper.component.html25
-rw-r--r--client/src/app/signup/custom-stepper.component.scss66
-rw-r--r--client/src/app/signup/custom-stepper.component.ts19
-rw-r--r--client/src/app/signup/signup-step-channel.component.html50
-rw-r--r--client/src/app/signup/signup-step-channel.component.ts40
-rw-r--r--client/src/app/signup/signup-step-user.component.html54
-rw-r--r--client/src/app/signup/signup-step-user.component.ts37
-rw-r--r--client/src/app/signup/signup.component.html77
-rw-r--r--client/src/app/signup/signup.component.scss33
-rw-r--r--client/src/app/signup/signup.component.ts61
-rw-r--r--client/src/app/signup/signup.module.ts15
-rw-r--r--client/src/app/signup/success.component.html8
-rw-r--r--client/src/app/signup/success.component.scss74
-rw-r--r--client/src/app/signup/success.component.ts10
-rw-r--r--client/src/sass/include/_mixins.scss7
-rw-r--r--server/middlewares/validators/users.ts6
-rw-r--r--server/tests/api/check-params/users.ts7
19 files changed, 502 insertions, 93 deletions
diff --git a/client/src/app/shared/forms/peertube-checkbox.component.scss b/client/src/app/shared/forms/peertube-checkbox.component.scss
index ea321ee65..84ea788af 100644
--- a/client/src/app/shared/forms/peertube-checkbox.component.scss
+++ b/client/src/app/shared/forms/peertube-checkbox.component.scss
@@ -14,9 +14,6 @@
14 14
15 input { 15 input {
16 @include peertube-checkbox(1px); 16 @include peertube-checkbox(1px);
17
18 width: 10px;
19 margin-right: 10px;
20 } 17 }
21 } 18 }
22 19
diff --git a/client/src/app/shared/users/user.service.ts b/client/src/app/shared/users/user.service.ts
index cc5c051f1..20883456f 100644
--- a/client/src/app/shared/users/user.service.ts
+++ b/client/src/app/shared/users/user.service.ts
@@ -9,6 +9,7 @@ import { Avatar } from '../../../../../shared/models/avatars/avatar.model'
9import { SortMeta } from 'primeng/api' 9import { SortMeta } from 'primeng/api'
10import { BytesPipe } from 'ngx-pipes' 10import { BytesPipe } from 'ngx-pipes'
11import { I18n } from '@ngx-translate/i18n-polyfill' 11import { I18n } from '@ngx-translate/i18n-polyfill'
12import { UserRegister } from '@shared/models/users/user-register.model'
12 13
13@Injectable() 14@Injectable()
14export class UserService { 15export class UserService {
@@ -64,7 +65,7 @@ export class UserService {
64 .pipe(catchError(err => this.restExtractor.handleError(err))) 65 .pipe(catchError(err => this.restExtractor.handleError(err)))
65 } 66 }
66 67
67 signup (userCreate: UserCreate) { 68 signup (userCreate: UserRegister) {
68 return this.authHttp.post(UserService.BASE_USERS_URL + 'register', userCreate) 69 return this.authHttp.post(UserService.BASE_USERS_URL + 'register', userCreate)
69 .pipe( 70 .pipe(
70 map(this.restExtractor.extractDataBool), 71 map(this.restExtractor.extractDataBool),
diff --git a/client/src/app/signup/custom-stepper.component.html b/client/src/app/signup/custom-stepper.component.html
new file mode 100644
index 000000000..bf507fc4f
--- /dev/null
+++ b/client/src/app/signup/custom-stepper.component.html
@@ -0,0 +1,25 @@
1<section class="container">
2 <header>
3 <ng-container *ngFor="let step of steps; let i = index; let isLast = last;">
4 <div
5 class="step-info" [ngClass]="{ active: selectedIndex === i, completed: isCompleted(step) }"
6 (click)="onClick(i)"
7 >
8 <div class="step-index">
9 <ng-container *ngIf="!isCompleted(step)">{{ i + 1 }}</ng-container>
10 <my-global-icon *ngIf="isCompleted(step)" iconName="tick"></my-global-icon>
11 </div>
12
13 <div class="step-label">{{ step.label }}</div>
14 </div>
15
16 <!-- Do no display if this is the last child -->
17 <div *ngIf="!isLast" class="connector"></div>
18 </ng-container>
19 </header>
20
21 <div [style.display]="selected ? 'block' : 'none'">
22 <ng-container [ngTemplateOutlet]="selected.content"></ng-container>
23 </div>
24
25</section>
diff --git a/client/src/app/signup/custom-stepper.component.scss b/client/src/app/signup/custom-stepper.component.scss
new file mode 100644
index 000000000..2371c8ae5
--- /dev/null
+++ b/client/src/app/signup/custom-stepper.component.scss
@@ -0,0 +1,66 @@
1@import '_variables';
2@import '_mixins';
3
4$grey-color: #9CA3AB;
5$index-block-height: 32px;
6
7header {
8 display: flex;
9 justify-content: space-between;
10 font-size: 15px;
11 margin-bottom: 30px;
12
13 .step-info {
14 color: $grey-color;
15 display: flex;
16 flex-direction: column;
17 align-items: center;
18 width: $index-block-height;
19
20 .step-index {
21 display: flex;
22 justify-content: center;
23 align-items: center;
24 width: $index-block-height;
25 height: $index-block-height;
26 border-radius: 100px;
27 border: 2px solid $grey-color;
28 margin-bottom: 10px;
29
30 my-global-icon {
31 @include apply-svg-color(var(--mainBackgroundColor));
32
33 width: 22px;
34 height: 22px;
35 }
36 }
37
38 .step-label {
39 width: max-content;
40 }
41
42 &.active,
43 &.completed {
44 .step-index {
45 border-color: var(--mainColor);
46 background-color: var(--mainColor);
47 color: var(--mainBackgroundColor);
48 }
49
50 .step-label {
51 color: var(--mainColor);
52 }
53 }
54
55 &.completed {
56 cursor: pointer;
57 }
58 }
59
60 .connector {
61 flex: auto;
62 margin: $index-block-height/2 10px 0 10px;
63 height: 2px;
64 background-color: $grey-color;
65 }
66}
diff --git a/client/src/app/signup/custom-stepper.component.ts b/client/src/app/signup/custom-stepper.component.ts
new file mode 100644
index 000000000..2ae40f3a9
--- /dev/null
+++ b/client/src/app/signup/custom-stepper.component.ts
@@ -0,0 +1,19 @@
1import { Component } from '@angular/core'
2import { CdkStep, CdkStepper } from '@angular/cdk/stepper'
3
4@Component({
5 selector: 'my-custom-stepper',
6 templateUrl: './custom-stepper.component.html',
7 styleUrls: [ './custom-stepper.component.scss' ],
8 providers: [ { provide: CdkStepper, useExisting: CustomStepperComponent } ]
9})
10export class CustomStepperComponent extends CdkStepper {
11
12 onClick (index: number): void {
13 this.selectedIndex = index
14 }
15
16 isCompleted (step: CdkStep) {
17 return step.stepControl && step.stepControl.dirty && step.stepControl.valid
18 }
19}
diff --git a/client/src/app/signup/signup-step-channel.component.html b/client/src/app/signup/signup-step-channel.component.html
new file mode 100644
index 000000000..68ea4473a
--- /dev/null
+++ b/client/src/app/signup/signup-step-channel.component.html
@@ -0,0 +1,50 @@
1<form role="form" [formGroup]="form">
2
3 <div class="channel-explanations">
4 <p i18n>
5 A channel is an entity in which you upload your videos. Creating several of them helps you to organize and separate your content.<br />
6 For example, you could decide to have a channel to publish your piano concerts, and another channel in which you publish your videos talking about ecology.
7 </p>
8
9 <p>
10 Other users can decide to subscribe any channel they want, to be notified when you publish a new video.
11 </p>
12 </div>
13
14 <div class="form-group">
15 <label for="name" i18n>Channel name</label>
16
17 <div class="input-group">
18 <input
19 type="text" id="name" i18n-placeholder placeholder="Example: my_super_channel"
20 formControlName="name" [ngClass]="{ 'input-error': formErrors['name'] }"
21 >
22 <div class="input-group-append">
23 <span class="input-group-text">@{{ instanceHost }}</span>
24 </div>
25 </div>
26
27 <div *ngIf="formErrors.name" class="form-error">
28 {{ formErrors.name }}
29 </div>
30
31 <div *ngIf="isSameThanUsername()" class="form-error" i18n>
32 Channel name cannot be the same than your account name. You can click on the first step to update your account name.
33 </div>
34 </div>
35
36 <div class="form-group">
37 <label for="displayName" i18n>Channel display name</label>
38
39 <div class="input-group">
40 <input
41 type="text" id="displayName"
42 formControlName="displayName" [ngClass]="{ 'input-error': formErrors['displayName'] }"
43 >
44 </div>
45
46 <div *ngIf="formErrors.displayName" class="form-error">
47 {{ formErrors.displayName }}
48 </div>
49 </div>
50</form>
diff --git a/client/src/app/signup/signup-step-channel.component.ts b/client/src/app/signup/signup-step-channel.component.ts
new file mode 100644
index 000000000..a49b7f36f
--- /dev/null
+++ b/client/src/app/signup/signup-step-channel.component.ts
@@ -0,0 +1,40 @@
1import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
2import { AuthService } from '@app/core'
3import { FormReactive, VideoChannelValidatorsService } from '../shared'
4import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
5import { FormGroup } from '@angular/forms'
6
7@Component({
8 selector: 'my-signup-step-channel',
9 templateUrl: './signup-step-channel.component.html',
10 styleUrls: [ './signup.component.scss' ]
11})
12export class SignupStepChannelComponent extends FormReactive implements OnInit {
13 @Input() username: string
14 @Output() formBuilt = new EventEmitter<FormGroup>()
15
16 constructor (
17 protected formValidatorService: FormValidatorService,
18 private authService: AuthService,
19 private videoChannelValidatorsService: VideoChannelValidatorsService
20 ) {
21 super()
22 }
23
24 get instanceHost () {
25 return window.location.host
26 }
27
28 isSameThanUsername () {
29 return this.username && this.username === this.form.value['name']
30 }
31
32 ngOnInit () {
33 this.buildForm({
34 name: this.videoChannelValidatorsService.VIDEO_CHANNEL_NAME,
35 displayName: this.videoChannelValidatorsService.VIDEO_CHANNEL_DISPLAY_NAME
36 })
37
38 setTimeout(() => this.formBuilt.emit(this.form))
39 }
40}
diff --git a/client/src/app/signup/signup-step-user.component.html b/client/src/app/signup/signup-step-user.component.html
new file mode 100644
index 000000000..cd0c78bfa
--- /dev/null
+++ b/client/src/app/signup/signup-step-user.component.html
@@ -0,0 +1,54 @@
1<form role="form" [formGroup]="form">
2
3 <div class="form-group">
4 <label for="username" i18n>Username</label>
5
6 <div class="input-group">
7 <input
8 type="text" id="username" i18n-placeholder placeholder="Example: jane_doe"
9 formControlName="username" [ngClass]="{ 'input-error': formErrors['username'] }"
10 >
11 <div class="input-group-append">
12 <span class="input-group-text">@{{ instanceHost }}</span>
13 </div>
14 </div>
15
16 <div *ngIf="formErrors.username" class="form-error">
17 {{ formErrors.username }}
18 </div>
19 </div>
20
21 <div class="form-group">
22 <label for="email" i18n>Email</label>
23 <input
24 type="text" id="email" i18n-placeholder placeholder="Email"
25 formControlName="email" [ngClass]="{ 'input-error': formErrors['email'] }"
26 >
27 <div *ngIf="formErrors.email" class="form-error">
28 {{ formErrors.email }}
29 </div>
30 </div>
31
32 <div class="form-group">
33 <label for="password" i18n>Password</label>
34 <input
35 type="password" id="password" i18n-placeholder placeholder="Password"
36 formControlName="password" [ngClass]="{ 'input-error': formErrors['password'] }"
37 >
38 <div *ngIf="formErrors.password" class="form-error">
39 {{ formErrors.password }}
40 </div>
41 </div>
42
43 <div class="form-group form-group-terms">
44 <my-peertube-checkbox
45 inputName="terms" formControlName="terms"
46 i18n-labelHtml
47 labelHtml="I am at least 16 years old and agree to the <a href='/about/instance#terms-section' target='_blank'rel='noopener noreferrer'>Terms</a> of this instance"
48 ></my-peertube-checkbox>
49
50 <div *ngIf="formErrors.terms" class="form-error">
51 {{ formErrors.terms }}
52 </div>
53 </div>
54</form>
diff --git a/client/src/app/signup/signup-step-user.component.ts b/client/src/app/signup/signup-step-user.component.ts
new file mode 100644
index 000000000..54855d8a7
--- /dev/null
+++ b/client/src/app/signup/signup-step-user.component.ts
@@ -0,0 +1,37 @@
1import { Component, EventEmitter, OnInit, Output } from '@angular/core'
2import { AuthService } from '@app/core'
3import { FormReactive, UserValidatorsService } from '../shared'
4import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
5import { FormGroup } from '@angular/forms'
6
7@Component({
8 selector: 'my-signup-step-user',
9 templateUrl: './signup-step-user.component.html',
10 styleUrls: [ './signup.component.scss' ]
11})
12export class SignupStepUserComponent extends FormReactive implements OnInit {
13 @Output() formBuilt = new EventEmitter<FormGroup>()
14
15 constructor (
16 protected formValidatorService: FormValidatorService,
17 private authService: AuthService,
18 private userValidatorsService: UserValidatorsService
19 ) {
20 super()
21 }
22
23 get instanceHost () {
24 return window.location.host
25 }
26
27 ngOnInit () {
28 this.buildForm({
29 username: this.userValidatorsService.USER_USERNAME,
30 password: this.userValidatorsService.USER_PASSWORD,
31 email: this.userValidatorsService.USER_EMAIL,
32 terms: this.userValidatorsService.USER_TERMS
33 })
34
35 setTimeout(() => this.formBuilt.emit(this.form))
36 }
37}
diff --git a/client/src/app/signup/signup.component.html b/client/src/app/signup/signup.component.html
index 07d24b381..ae3a595e9 100644
--- a/client/src/app/signup/signup.component.html
+++ b/client/src/app/signup/signup.component.html
@@ -4,64 +4,35 @@
4 Create an account 4 Create an account
5 </div> 5 </div>
6 6
7 <div *ngIf="info" class="alert alert-info">{{ info }}</div>
8 <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
9
10 <div class="d-flex justify-content-left flex-wrap">
11 <form role="form" (ngSubmit)="signup()" [formGroup]="form">
12 <div class="form-group">
13 <label for="username" i18n>Username</label>
14
15 <div class="input-group">
16 <input
17 type="text" id="username" i18n-placeholder placeholder="Example: jane_doe"
18 formControlName="username" [ngClass]="{ 'input-error': formErrors['username'] }"
19 >
20 <div class="input-group-append">
21 <span class="input-group-text">@{{ instanceHost }}</span>
22 </div>
23 </div>
24
25 <div *ngIf="formErrors.username" class="form-error">
26 {{ formErrors.username }}
27 </div>
28 </div>
29 7
30 <div class="form-group"> 8 <my-success *ngIf="signupDone"></my-success>
31 <label for="email" i18n>Email</label> 9 <div *ngIf="info" class="alert alert-info">{{ info }}</div>
32 <input 10 <div *ngIf="success" class="alert alert-success">{{ success }}</div>
33 type="text" id="email" i18n-placeholder placeholder="Email"
34 formControlName="email" [ngClass]="{ 'input-error': formErrors['email'] }"
35 >
36 <div *ngIf="formErrors.email" class="form-error">
37 {{ formErrors.email }}
38 </div>
39 </div>
40 11
41 <div class="form-group"> 12 <div class="wrapper" *ngIf="!signupDone">
42 <label for="password" i18n>Password</label> 13 <div>
43 <input 14 <my-custom-stepper linear *ngIf="!signupDone">
44 type="password" id="password" i18n-placeholder placeholder="Password" 15 <cdk-step [stepControl]="formStepUser" i18n-label label="User information">
45 formControlName="password" [ngClass]="{ 'input-error': formErrors['password'] }" 16 <my-signup-step-user (formBuilt)="onUserFormBuilt($event)"></my-signup-step-user>
46 >
47 <div *ngIf="formErrors.password" class="form-error">
48 {{ formErrors.password }}
49 </div>
50 </div>
51 17
52 <div class="form-group form-group-terms"> 18 <button i18n cdkStepperNext [disabled]="!formStepUser || !formStepUser.valid">Next</button>
53 <my-peertube-checkbox 19 </cdk-step>
54 inputName="terms" formControlName="terms"
55 i18n-labelHtml labelHtml="I am at least 16 years old and agree to the <a href='/about/instance#terms-section' target='_blank'rel='noopener noreferrer'>Terms</a> of this instance"
56 ></my-peertube-checkbox>
57 20
58 <div *ngIf="formErrors.terms" class="form-error"> 21 <cdk-step [stepControl]="formStepChannel" i18n-label label="Channel information">
59 {{ formErrors.terms }} 22 <my-signup-step-channel (formBuilt)="onChannelFormBuilt($event)" [username]="getUsername()"></my-signup-step-channel>
60 </div>
61 </div>
62 23
63 <input type="submit" i18n-value value="Signup" [disabled]="!form.valid || signupDone"> 24 <button i18n cdkStepperNext (click)="signup()"
64 </form> 25 [disabled]="!formStepChannel || !formStepChannel.valid || hasSameChannelAndAccountNames()"
26 >
27 Create my account
28 </button>
29 </cdk-step>
30
31 <cdk-step i18n-label label="Done" editable="false">
32 <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
33 </cdk-step>
34 </my-custom-stepper>
35 </div>
65 36
66 <div> 37 <div>
67 <label i18n>Features found on this instance</label> 38 <label i18n>Features found on this instance</label>
diff --git a/client/src/app/signup/signup.component.scss b/client/src/app/signup/signup.component.scss
index 90e1e8e74..6f61b78f7 100644
--- a/client/src/app/signup/signup.component.scss
+++ b/client/src/app/signup/signup.component.scss
@@ -1,16 +1,32 @@
1@import '_variables'; 1@import '_variables';
2@import '_mixins'; 2@import '_mixins';
3 3
4.alert {
5 font-size: 15px;
6 text-align: center;
7}
8
9.wrapper {
10 display: flex;
11 justify-content: space-between;
12 flex-wrap: wrap;
13
14 & > div {
15 margin-bottom: 40px;
16 width: 450px;
17
18 @media screen and (max-width: 500px) {
19 width: auto;
20 }
21 }
22}
23
4my-instance-features-table { 24my-instance-features-table {
5 display: block; 25 display: block;
6 26
7 margin-bottom: 40px; 27 margin-bottom: 40px;
8} 28}
9 29
10form {
11 margin: 0 60px 40px 0;
12}
13
14.form-group-terms { 30.form-group-terms {
15 margin: 30px 0; 31 margin: 30px 0;
16} 32}
@@ -25,15 +41,18 @@ form {
25 41
26input:not([type=submit]) { 42input:not([type=submit]) {
27 @include peertube-input-text(400px); 43 @include peertube-input-text(400px);
44
28 display: block; 45 display: block;
29 46
30 &#username { 47 &#username,
31 width: auto; 48 &#name {
49 width: auto !important;
32 flex-grow: 1; 50 flex-grow: 1;
33 } 51 }
34} 52}
35 53
36input[type=submit] { 54input[type=submit],
55button {
37 @include peertube-button; 56 @include peertube-button;
38 @include orange-button; 57 @include orange-button;
39} 58}
diff --git a/client/src/app/signup/signup.component.ts b/client/src/app/signup/signup.component.ts
index 13941ec79..11eaa8521 100644
--- a/client/src/app/signup/signup.component.ts
+++ b/client/src/app/signup/signup.component.ts
@@ -1,22 +1,25 @@
1import { Component, OnInit } from '@angular/core' 1import { Component } from '@angular/core'
2import { AuthService, Notifier, RedirectService, ServerService } from '@app/core' 2import { AuthService, Notifier, RedirectService, ServerService } from '@app/core'
3import { UserCreate } from '../../../../shared' 3import { UserService, UserValidatorsService } from '../shared'
4import { FormReactive, UserService, UserValidatorsService } from '../shared'
5import { I18n } from '@ngx-translate/i18n-polyfill' 4import { I18n } from '@ngx-translate/i18n-polyfill'
6import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' 5import { UserRegister } from '@shared/models/users/user-register.model'
6import { FormGroup } from '@angular/forms'
7 7
8@Component({ 8@Component({
9 selector: 'my-signup', 9 selector: 'my-signup',
10 templateUrl: './signup.component.html', 10 templateUrl: './signup.component.html',
11 styleUrls: [ './signup.component.scss' ] 11 styleUrls: [ './signup.component.scss' ]
12}) 12})
13export class SignupComponent extends FormReactive implements OnInit { 13export class SignupComponent {
14 info: string = null 14 info: string = null
15 error: string = null 15 error: string = null
16 success: string = null
16 signupDone = false 17 signupDone = false
17 18
19 formStepUser: FormGroup
20 formStepChannel: FormGroup
21
18 constructor ( 22 constructor (
19 protected formValidatorService: FormValidatorService,
20 private authService: AuthService, 23 private authService: AuthService,
21 private userValidatorsService: UserValidatorsService, 24 private userValidatorsService: UserValidatorsService,
22 private notifier: Notifier, 25 private notifier: Notifier,
@@ -25,47 +28,55 @@ export class SignupComponent extends FormReactive implements OnInit {
25 private redirectService: RedirectService, 28 private redirectService: RedirectService,
26 private i18n: I18n 29 private i18n: I18n
27 ) { 30 ) {
28 super()
29 }
30
31 get instanceHost () {
32 return window.location.host
33 } 31 }
34 32
35 get requiresEmailVerification () { 33 get requiresEmailVerification () {
36 return this.serverService.getConfig().signup.requiresEmailVerification 34 return this.serverService.getConfig().signup.requiresEmailVerification
37 } 35 }
38 36
39 ngOnInit () { 37 hasSameChannelAndAccountNames () {
40 this.buildForm({ 38 return this.getUsername() === this.getChannelName()
41 username: this.userValidatorsService.USER_USERNAME, 39 }
42 password: this.userValidatorsService.USER_PASSWORD, 40
43 email: this.userValidatorsService.USER_EMAIL, 41 getUsername () {
44 terms: this.userValidatorsService.USER_TERMS 42 if (!this.formStepUser) return undefined
45 }) 43
44 return this.formStepUser.value['username']
45 }
46
47 getChannelName () {
48 if (!this.formStepChannel) return undefined
49
50 return this.formStepChannel.value['name']
51 }
52
53 onUserFormBuilt (form: FormGroup) {
54 this.formStepUser = form
55 }
56
57 onChannelFormBuilt (form: FormGroup) {
58 this.formStepChannel = form
46 } 59 }
47 60
48 signup () { 61 signup () {
49 this.error = null 62 this.error = null
50 63
51 const userCreate: UserCreate = this.form.value 64 const body: UserRegister = Object.assign(this.formStepUser.value, this.formStepChannel.value)
52 65
53 this.userService.signup(userCreate).subscribe( 66 this.userService.signup(body).subscribe(
54 () => { 67 () => {
55 this.signupDone = true 68 this.signupDone = true
56 69
57 if (this.requiresEmailVerification) { 70 if (this.requiresEmailVerification) {
58 this.info = this.i18n('Welcome! Now please check your emails to verify your account and complete signup.') 71 this.info = this.i18n('Now please check your emails to verify your account and complete signup.')
59 return 72 return
60 } 73 }
61 74
62 // Auto login 75 // Auto login
63 this.authService.login(userCreate.username, userCreate.password) 76 this.authService.login(body.username, body.password)
64 .subscribe( 77 .subscribe(
65 () => { 78 () => {
66 this.notifier.success(this.i18n('You are now logged in as {{username}}!', { username: userCreate.username })) 79 this.success = this.i18n('You are now logged in as {{username}}!', { username: body.username })
67
68 this.redirectService.redirectToHomepage()
69 }, 80 },
70 81
71 err => this.error = err.message 82 err => this.error = err.message
diff --git a/client/src/app/signup/signup.module.ts b/client/src/app/signup/signup.module.ts
index 61560ddcf..fccaf7ce1 100644
--- a/client/src/app/signup/signup.module.ts
+++ b/client/src/app/signup/signup.module.ts
@@ -1,17 +1,26 @@
1import { NgModule } from '@angular/core' 1import { NgModule } from '@angular/core'
2
3import { SignupRoutingModule } from './signup-routing.module' 2import { SignupRoutingModule } from './signup-routing.module'
4import { SignupComponent } from './signup.component' 3import { SignupComponent } from './signup.component'
5import { SharedModule } from '../shared' 4import { SharedModule } from '../shared'
5import { CdkStepperModule } from '@angular/cdk/stepper'
6import { SignupStepChannelComponent } from '@app/signup/signup-step-channel.component'
7import { SignupStepUserComponent } from '@app/signup/signup-step-user.component'
8import { CustomStepperComponent } from '@app/signup/custom-stepper.component'
9import { SuccessComponent } from '@app/signup/success.component'
6 10
7@NgModule({ 11@NgModule({
8 imports: [ 12 imports: [
9 SignupRoutingModule, 13 SignupRoutingModule,
10 SharedModule 14 SharedModule,
15 CdkStepperModule
11 ], 16 ],
12 17
13 declarations: [ 18 declarations: [
14 SignupComponent 19 SignupComponent,
20 CustomStepperComponent,
21 SuccessComponent,
22 SignupStepChannelComponent,
23 SignupStepUserComponent
15 ], 24 ],
16 25
17 exports: [ 26 exports: [
diff --git a/client/src/app/signup/success.component.html b/client/src/app/signup/success.component.html
new file mode 100644
index 000000000..68eb72b61
--- /dev/null
+++ b/client/src/app/signup/success.component.html
@@ -0,0 +1,8 @@
1<!-- Thanks: Amit Singh Sansoya from https://codepen.io/amit3200/pen/zWMJOO -->
2
3<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 130.2 130.2">
4 <circle class="path circle" fill="none" stroke="#73AF55" stroke-width="6" stroke-miterlimit="10" cx="65.1" cy="65.1" r="62.1"/>
5 <polyline class="path check" fill="none" stroke="#73AF55" stroke-width="6" stroke-linecap="round" stroke-miterlimit="10" points="100.2,40.2 51.5,88.8 29.8,67.5 "/>
6</svg>
7
8<p class="success">Welcome on PeerTube!</p>
diff --git a/client/src/app/signup/success.component.scss b/client/src/app/signup/success.component.scss
new file mode 100644
index 000000000..7c66e08cf
--- /dev/null
+++ b/client/src/app/signup/success.component.scss
@@ -0,0 +1,74 @@
1svg {
2 width: 100px;
3 display: block;
4 margin: 40px auto 0;
5}
6
7.path {
8 stroke-dasharray: 1000;
9 stroke-dashoffset: 0;
10
11 &.circle {
12 -webkit-animation: dash .9s ease-in-out;
13 animation: dash .9s ease-in-out;
14 }
15
16 &.line {
17 stroke-dashoffset: 1000;
18 -webkit-animation: dash .9s .35s ease-in-out forwards;
19 animation: dash .9s .35s ease-in-out forwards;
20 }
21
22 &.check {
23 stroke-dashoffset: -100;
24 -webkit-animation: dash-check .9s .35s ease-in-out forwards;
25 animation: dash-check .9s .35s ease-in-out forwards;
26 }
27}
28
29p {
30 text-align: center;
31 margin: 20px 0 60px;
32 font-size: 1.25em;
33
34 &.success {
35 color: #73AF55;
36 }
37}
38
39
40@-webkit-keyframes dash {
41 0% {
42 stroke-dashoffset: 1000;
43 }
44 100% {
45 stroke-dashoffset: 0;
46 }
47}
48
49@keyframes dash {
50 0% {
51 stroke-dashoffset: 1000;
52 }
53 100% {
54 stroke-dashoffset: 0;
55 }
56}
57
58@-webkit-keyframes dash-check {
59 0% {
60 stroke-dashoffset: -100;
61 }
62 100% {
63 stroke-dashoffset: 900;
64 }
65}
66
67@keyframes dash-check {
68 0% {
69 stroke-dashoffset: -100;
70 }
71 100% {
72 stroke-dashoffset: 900;
73 }
74}
diff --git a/client/src/app/signup/success.component.ts b/client/src/app/signup/success.component.ts
new file mode 100644
index 000000000..2674e1e30
--- /dev/null
+++ b/client/src/app/signup/success.component.ts
@@ -0,0 +1,10 @@
1import { Component } from '@angular/core'
2
3@Component({
4 selector: 'my-success',
5 templateUrl: './success.component.html',
6 styleUrls: [ './success.component.scss' ]
7})
8export class SuccessComponent {
9
10}
diff --git a/client/src/sass/include/_mixins.scss b/client/src/sass/include/_mixins.scss
index 262a8136f..228a6116e 100644
--- a/client/src/sass/include/_mixins.scss
+++ b/client/src/sass/include/_mixins.scss
@@ -331,7 +331,12 @@
331} 331}
332 332
333@mixin peertube-checkbox ($border-width) { 333@mixin peertube-checkbox ($border-width) {
334 display: none; 334 opacity: 0;
335 width: 0;
336
337 &:focus + span {
338 outline: auto;
339 }
335 340
336 & + span { 341 & + span {
337 position: relative; 342 position: relative;
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts
index b58dcc0d6..7a081af33 100644
--- a/server/middlewares/validators/users.ts
+++ b/server/middlewares/validators/users.ts
@@ -70,6 +70,12 @@ const usersRegisterValidator = [
70 .end() 70 .end()
71 } 71 }
72 72
73 if (body.channel.name === body.username) {
74 return res.status(400)
75 .send({ error: 'Channel name cannot be the same than user username.' })
76 .end()
77 }
78
73 const existing = await ActorModel.loadLocalByName(body.channel.name) 79 const existing = await ActorModel.loadLocalByName(body.channel.name)
74 if (existing) { 80 if (existing) {
75 return res.status(409) 81 return res.status(409)
diff --git a/server/tests/api/check-params/users.ts b/server/tests/api/check-params/users.ts
index d26032ea5..95097817b 100644
--- a/server/tests/api/check-params/users.ts
+++ b/server/tests/api/check-params/users.ts
@@ -737,6 +737,13 @@ describe('Test users API validators', function () {
737 await makePostBodyRequest({ url: server.url, path: registrationPath, token: server.accessToken, fields }) 737 await makePostBodyRequest({ url: server.url, path: registrationPath, token: server.accessToken, fields })
738 }) 738 })
739 739
740 it('Should fail with a channel name that is the same than user username', async function () {
741 const source = { username: 'super_user', channel: { name: 'super_user', displayName: 'display name' } }
742 const fields = immutableAssign(baseCorrectParams, source)
743
744 await makePostBodyRequest({ url: server.url, path: registrationPath, token: server.accessToken, fields })
745 })
746
740 it('Should fail with an existing channel', async function () { 747 it('Should fail with an existing channel', async function () {
741 const videoChannelAttributesArg = { name: 'existing_channel', displayName: 'hello', description: 'super description' } 748 const videoChannelAttributesArg = { name: 'existing_channel', displayName: 'hello', description: 'super description' }
742 await addVideoChannel(server.url, server.accessToken, videoChannelAttributesArg) 749 await addVideoChannel(server.url, server.accessToken, videoChannelAttributesArg)