Multi step registration
authorChocobozzz <me@florianbigard.com>
Wed, 29 May 2019 09:03:01 +0000 (11:03 +0200)
committerChocobozzz <me@florianbigard.com>
Wed, 29 May 2019 09:19:54 +0000 (11:19 +0200)
19 files changed:
client/src/app/shared/forms/peertube-checkbox.component.scss
client/src/app/shared/users/user.service.ts
client/src/app/signup/custom-stepper.component.html [new file with mode: 0644]
client/src/app/signup/custom-stepper.component.scss [new file with mode: 0644]
client/src/app/signup/custom-stepper.component.ts [new file with mode: 0644]
client/src/app/signup/signup-step-channel.component.html [new file with mode: 0644]
client/src/app/signup/signup-step-channel.component.ts [new file with mode: 0644]
client/src/app/signup/signup-step-user.component.html [new file with mode: 0644]
client/src/app/signup/signup-step-user.component.ts [new file with mode: 0644]
client/src/app/signup/signup.component.html
client/src/app/signup/signup.component.scss
client/src/app/signup/signup.component.ts
client/src/app/signup/signup.module.ts
client/src/app/signup/success.component.html [new file with mode: 0644]
client/src/app/signup/success.component.scss [new file with mode: 0644]
client/src/app/signup/success.component.ts [new file with mode: 0644]
client/src/sass/include/_mixins.scss
server/middlewares/validators/users.ts
server/tests/api/check-params/users.ts

index ea321ee6581c4d2f29e3d272f0c335172eeddaf0..84ea788af39e105bc81d9be4bd928ffc04369feb 100644 (file)
@@ -14,9 +14,6 @@
 
     input {
       @include peertube-checkbox(1px);
-
-      width: 10px;
-      margin-right: 10px;
     }
   }
 
index cc5c051f173b040386ba9897a6541d0154da0c04..20883456fd68edd5a100857a32de5739a847771b 100644 (file)
@@ -9,6 +9,7 @@ import { Avatar } from '../../../../../shared/models/avatars/avatar.model'
 import { SortMeta } from 'primeng/api'
 import { BytesPipe } from 'ngx-pipes'
 import { I18n } from '@ngx-translate/i18n-polyfill'
+import { UserRegister } from '@shared/models/users/user-register.model'
 
 @Injectable()
 export class UserService {
@@ -64,7 +65,7 @@ export class UserService {
                .pipe(catchError(err => this.restExtractor.handleError(err)))
   }
 
-  signup (userCreate: UserCreate) {
+  signup (userCreate: UserRegister) {
     return this.authHttp.post(UserService.BASE_USERS_URL + 'register', userCreate)
                .pipe(
                  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 (file)
index 0000000..bf507fc
--- /dev/null
@@ -0,0 +1,25 @@
+<section class="container">
+  <header>
+    <ng-container *ngFor="let step of steps; let i = index; let isLast = last;">
+      <div
+        class="step-info" [ngClass]="{ active: selectedIndex === i, completed: isCompleted(step) }"
+        (click)="onClick(i)"
+      >
+        <div class="step-index">
+          <ng-container *ngIf="!isCompleted(step)">{{ i + 1 }}</ng-container>
+          <my-global-icon *ngIf="isCompleted(step)" iconName="tick"></my-global-icon>
+        </div>
+
+        <div class="step-label">{{ step.label }}</div>
+      </div>
+
+      <!-- Do no display if this is the last child -->
+      <div *ngIf="!isLast" class="connector"></div>
+    </ng-container>
+  </header>
+
+  <div [style.display]="selected ? 'block' : 'none'">
+    <ng-container [ngTemplateOutlet]="selected.content"></ng-container>
+  </div>
+
+</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 (file)
index 0000000..2371c8a
--- /dev/null
@@ -0,0 +1,66 @@
+@import '_variables';
+@import '_mixins';
+
+$grey-color: #9CA3AB;
+$index-block-height: 32px;
+
+header {
+  display: flex;
+  justify-content: space-between;
+  font-size: 15px;
+  margin-bottom: 30px;
+
+  .step-info {
+    color: $grey-color;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    width: $index-block-height;
+
+    .step-index {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      width: $index-block-height;
+      height: $index-block-height;
+      border-radius: 100px;
+      border: 2px solid $grey-color;
+      margin-bottom: 10px;
+
+      my-global-icon {
+        @include apply-svg-color(var(--mainBackgroundColor));
+
+        width: 22px;
+        height: 22px;
+      }
+    }
+
+    .step-label {
+      width: max-content;
+    }
+
+    &.active,
+    &.completed {
+      .step-index {
+        border-color: var(--mainColor);
+        background-color: var(--mainColor);
+        color: var(--mainBackgroundColor);
+      }
+
+      .step-label {
+        color: var(--mainColor);
+      }
+    }
+
+    &.completed {
+      cursor: pointer;
+    }
+  }
+
+  .connector {
+    flex: auto;
+    margin: $index-block-height/2 10px 0 10px;
+    height: 2px;
+    background-color: $grey-color;
+  }
+}
diff --git a/client/src/app/signup/custom-stepper.component.ts b/client/src/app/signup/custom-stepper.component.ts
new file mode 100644 (file)
index 0000000..2ae40f3
--- /dev/null
@@ -0,0 +1,19 @@
+import { Component } from '@angular/core'
+import { CdkStep, CdkStepper } from '@angular/cdk/stepper'
+
+@Component({
+  selector: 'my-custom-stepper',
+  templateUrl: './custom-stepper.component.html',
+  styleUrls: [ './custom-stepper.component.scss' ],
+  providers: [ { provide: CdkStepper, useExisting: CustomStepperComponent } ]
+})
+export class CustomStepperComponent extends CdkStepper {
+
+  onClick (index: number): void {
+    this.selectedIndex = index
+  }
+
+  isCompleted (step: CdkStep) {
+    return step.stepControl && step.stepControl.dirty && step.stepControl.valid
+  }
+}
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 (file)
index 0000000..68ea447
--- /dev/null
@@ -0,0 +1,50 @@
+<form role="form" [formGroup]="form">
+
+  <div class="channel-explanations">
+    <p i18n>
+      A channel is an entity in which you upload your videos. Creating several of them helps you to organize and separate your content.<br />
+      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.
+    </p>
+
+    <p>
+      Other users can decide to subscribe any channel they want, to be notified when you publish a new video.
+    </p>
+  </div>
+
+  <div class="form-group">
+    <label for="name" i18n>Channel name</label>
+
+    <div class="input-group">
+      <input
+        type="text" id="name" i18n-placeholder placeholder="Example: my_super_channel"
+        formControlName="name" [ngClass]="{ 'input-error': formErrors['name'] }"
+      >
+      <div class="input-group-append">
+        <span class="input-group-text">@{{ instanceHost }}</span>
+      </div>
+    </div>
+
+    <div *ngIf="formErrors.name" class="form-error">
+      {{ formErrors.name }}
+    </div>
+
+    <div *ngIf="isSameThanUsername()" class="form-error" i18n>
+      Channel name cannot be the same than your account name. You can click on the first step to update your account name.
+    </div>
+  </div>
+
+  <div class="form-group">
+    <label for="displayName" i18n>Channel display name</label>
+
+    <div class="input-group">
+      <input
+        type="text" id="displayName"
+        formControlName="displayName" [ngClass]="{ 'input-error': formErrors['displayName'] }"
+      >
+    </div>
+
+    <div *ngIf="formErrors.displayName" class="form-error">
+      {{ formErrors.displayName }}
+    </div>
+  </div>
+</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 (file)
index 0000000..a49b7f3
--- /dev/null
@@ -0,0 +1,40 @@
+import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
+import { AuthService } from '@app/core'
+import { FormReactive, VideoChannelValidatorsService } from '../shared'
+import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
+import { FormGroup } from '@angular/forms'
+
+@Component({
+  selector: 'my-signup-step-channel',
+  templateUrl: './signup-step-channel.component.html',
+  styleUrls: [ './signup.component.scss' ]
+})
+export class SignupStepChannelComponent extends FormReactive implements OnInit {
+  @Input() username: string
+  @Output() formBuilt = new EventEmitter<FormGroup>()
+
+  constructor (
+    protected formValidatorService: FormValidatorService,
+    private authService: AuthService,
+    private videoChannelValidatorsService: VideoChannelValidatorsService
+  ) {
+    super()
+  }
+
+  get instanceHost () {
+    return window.location.host
+  }
+
+  isSameThanUsername () {
+    return this.username && this.username === this.form.value['name']
+  }
+
+  ngOnInit () {
+    this.buildForm({
+      name: this.videoChannelValidatorsService.VIDEO_CHANNEL_NAME,
+      displayName: this.videoChannelValidatorsService.VIDEO_CHANNEL_DISPLAY_NAME
+    })
+
+    setTimeout(() => this.formBuilt.emit(this.form))
+  }
+}
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 (file)
index 0000000..cd0c78b
--- /dev/null
@@ -0,0 +1,54 @@
+<form role="form" [formGroup]="form">
+
+  <div class="form-group">
+    <label for="username" i18n>Username</label>
+
+    <div class="input-group">
+      <input
+        type="text" id="username" i18n-placeholder placeholder="Example: jane_doe"
+        formControlName="username" [ngClass]="{ 'input-error': formErrors['username'] }"
+      >
+      <div class="input-group-append">
+        <span class="input-group-text">@{{ instanceHost }}</span>
+      </div>
+    </div>
+
+    <div *ngIf="formErrors.username" class="form-error">
+      {{ formErrors.username }}
+    </div>
+  </div>
+
+  <div class="form-group">
+    <label for="email" i18n>Email</label>
+    <input
+      type="text" id="email" i18n-placeholder placeholder="Email"
+      formControlName="email" [ngClass]="{ 'input-error': formErrors['email'] }"
+    >
+    <div *ngIf="formErrors.email" class="form-error">
+      {{ formErrors.email }}
+    </div>
+  </div>
+
+  <div class="form-group">
+    <label for="password" i18n>Password</label>
+    <input
+      type="password" id="password" i18n-placeholder placeholder="Password"
+      formControlName="password" [ngClass]="{ 'input-error': formErrors['password'] }"
+    >
+    <div *ngIf="formErrors.password" class="form-error">
+      {{ formErrors.password }}
+    </div>
+  </div>
+
+  <div class="form-group form-group-terms">
+    <my-peertube-checkbox
+      inputName="terms" formControlName="terms"
+      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"
+    ></my-peertube-checkbox>
+
+    <div *ngIf="formErrors.terms" class="form-error">
+      {{ formErrors.terms }}
+    </div>
+  </div>
+</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 (file)
index 0000000..54855d8
--- /dev/null
@@ -0,0 +1,37 @@
+import { Component, EventEmitter, OnInit, Output } from '@angular/core'
+import { AuthService } from '@app/core'
+import { FormReactive, UserValidatorsService } from '../shared'
+import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
+import { FormGroup } from '@angular/forms'
+
+@Component({
+  selector: 'my-signup-step-user',
+  templateUrl: './signup-step-user.component.html',
+  styleUrls: [ './signup.component.scss' ]
+})
+export class SignupStepUserComponent extends FormReactive implements OnInit {
+  @Output() formBuilt = new EventEmitter<FormGroup>()
+
+  constructor (
+    protected formValidatorService: FormValidatorService,
+    private authService: AuthService,
+    private userValidatorsService: UserValidatorsService
+  ) {
+    super()
+  }
+
+  get instanceHost () {
+    return window.location.host
+  }
+
+  ngOnInit () {
+    this.buildForm({
+      username: this.userValidatorsService.USER_USERNAME,
+      password: this.userValidatorsService.USER_PASSWORD,
+      email: this.userValidatorsService.USER_EMAIL,
+      terms: this.userValidatorsService.USER_TERMS
+    })
+
+    setTimeout(() => this.formBuilt.emit(this.form))
+  }
+}
index 07d24b38132a56c0b706312fb25034da2b7d46a6..ae3a595e9fe023c3dd8add89a97d8c1e159de4c3 100644 (file)
@@ -4,64 +4,35 @@
     Create an account
   </div>
 
-  <div *ngIf="info" class="alert alert-info">{{ info }}</div>
-  <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
-
-  <div class="d-flex justify-content-left flex-wrap">
-    <form role="form" (ngSubmit)="signup()" [formGroup]="form">
-      <div class="form-group">
-        <label for="username" i18n>Username</label>
-
-        <div class="input-group">
-          <input
-            type="text" id="username" i18n-placeholder placeholder="Example: jane_doe"
-            formControlName="username" [ngClass]="{ 'input-error': formErrors['username'] }"
-          >
-          <div class="input-group-append">
-            <span class="input-group-text">@{{ instanceHost }}</span>
-          </div>
-        </div>
-
-        <div *ngIf="formErrors.username" class="form-error">
-          {{ formErrors.username }}
-        </div>
-      </div>
 
-      <div class="form-group">
-        <label for="email" i18n>Email</label>
-        <input
-          type="text" id="email" i18n-placeholder placeholder="Email"
-          formControlName="email" [ngClass]="{ 'input-error': formErrors['email'] }"
-        >
-        <div *ngIf="formErrors.email" class="form-error">
-          {{ formErrors.email }}
-        </div>
-      </div>
+  <my-success *ngIf="signupDone"></my-success>
+  <div *ngIf="info" class="alert alert-info">{{ info }}</div>
+  <div *ngIf="success" class="alert alert-success">{{ success }}</div>
 
-      <div class="form-group">
-        <label for="password" i18n>Password</label>
-        <input
-          type="password" id="password" i18n-placeholder placeholder="Password"
-          formControlName="password" [ngClass]="{ 'input-error': formErrors['password'] }"
-        >
-        <div *ngIf="formErrors.password" class="form-error">
-          {{ formErrors.password }}
-        </div>
-      </div>
+  <div class="wrapper" *ngIf="!signupDone">
+    <div>
+      <my-custom-stepper linear *ngIf="!signupDone">
+        <cdk-step [stepControl]="formStepUser" i18n-label label="User information">
+          <my-signup-step-user (formBuilt)="onUserFormBuilt($event)"></my-signup-step-user>
 
-      <div class="form-group form-group-terms">
-        <my-peertube-checkbox
-          inputName="terms" formControlName="terms"
-          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"
-        ></my-peertube-checkbox>
+          <button i18n cdkStepperNext [disabled]="!formStepUser || !formStepUser.valid">Next</button>
+        </cdk-step>
 
-        <div *ngIf="formErrors.terms" class="form-error">
-          {{ formErrors.terms }}
-        </div>
-      </div>
+        <cdk-step [stepControl]="formStepChannel" i18n-label label="Channel information">
+          <my-signup-step-channel (formBuilt)="onChannelFormBuilt($event)" [username]="getUsername()"></my-signup-step-channel>
 
-      <input type="submit" i18n-value value="Signup" [disabled]="!form.valid || signupDone">
-    </form>
+          <button i18n cdkStepperNext (click)="signup()"
+                  [disabled]="!formStepChannel || !formStepChannel.valid || hasSameChannelAndAccountNames()"
+          >
+            Create my account
+          </button>
+        </cdk-step>
+
+        <cdk-step i18n-label label="Done" editable="false">
+          <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
+        </cdk-step>
+      </my-custom-stepper>
+    </div>
 
     <div>
       <label i18n>Features found on this instance</label>
index 90e1e8e74d2f1e3a7c22298be42069cf379f589c..6f61b78f719df5a3c04eb7e6cbfc5461e7016327 100644 (file)
@@ -1,16 +1,32 @@
 @import '_variables';
 @import '_mixins';
 
+.alert {
+  font-size: 15px;
+  text-align: center;
+}
+
+.wrapper {
+  display: flex;
+  justify-content: space-between;
+  flex-wrap: wrap;
+
+  & > div {
+    margin-bottom: 40px;
+    width: 450px;
+
+    @media screen and (max-width: 500px) {
+      width: auto;
+    }
+  }
+}
+
 my-instance-features-table {
   display: block;
 
   margin-bottom: 40px;
 }
 
-form {
-  margin: 0 60px 40px 0;
-}
-
 .form-group-terms {
   margin: 30px 0;
 }
@@ -25,15 +41,18 @@ form {
 
 input:not([type=submit]) {
   @include peertube-input-text(400px);
+
   display: block;
 
-  &#username {
-    width: auto;
+  &#username,
+  &#name {
+    width: auto !important;
     flex-grow: 1;
   }
 }
 
-input[type=submit] {
+input[type=submit],
+button {
   @include peertube-button;
   @include orange-button;
 }
index 13941ec79542e978de2e9bb05a63ea0b2b3aeec8..11eaa85218ee56ce47c8e9448174a64d50e151c9 100644 (file)
@@ -1,22 +1,25 @@
-import { Component, OnInit } from '@angular/core'
+import { Component } from '@angular/core'
 import { AuthService, Notifier, RedirectService, ServerService } from '@app/core'
-import { UserCreate } from '../../../../shared'
-import { FormReactive, UserService, UserValidatorsService } from '../shared'
+import { UserService, UserValidatorsService } from '../shared'
 import { I18n } from '@ngx-translate/i18n-polyfill'
-import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
+import { UserRegister } from '@shared/models/users/user-register.model'
+import { FormGroup } from '@angular/forms'
 
 @Component({
   selector: 'my-signup',
   templateUrl: './signup.component.html',
   styleUrls: [ './signup.component.scss' ]
 })
-export class SignupComponent extends FormReactive implements OnInit {
+export class SignupComponent {
   info: string = null
   error: string = null
+  success: string = null
   signupDone = false
 
+  formStepUser: FormGroup
+  formStepChannel: FormGroup
+
   constructor (
-    protected formValidatorService: FormValidatorService,
     private authService: AuthService,
     private userValidatorsService: UserValidatorsService,
     private notifier: Notifier,
@@ -25,47 +28,55 @@ export class SignupComponent extends FormReactive implements OnInit {
     private redirectService: RedirectService,
     private i18n: I18n
   ) {
-    super()
-  }
-
-  get instanceHost () {
-    return window.location.host
   }
 
   get requiresEmailVerification () {
     return this.serverService.getConfig().signup.requiresEmailVerification
   }
 
-  ngOnInit () {
-    this.buildForm({
-      username: this.userValidatorsService.USER_USERNAME,
-      password: this.userValidatorsService.USER_PASSWORD,
-      email: this.userValidatorsService.USER_EMAIL,
-      terms: this.userValidatorsService.USER_TERMS
-    })
+  hasSameChannelAndAccountNames () {
+    return this.getUsername() === this.getChannelName()
+  }
+
+  getUsername () {
+    if (!this.formStepUser) return undefined
+
+    return this.formStepUser.value['username']
+  }
+
+  getChannelName () {
+    if (!this.formStepChannel) return undefined
+
+    return this.formStepChannel.value['name']
+  }
+
+  onUserFormBuilt (form: FormGroup) {
+    this.formStepUser = form
+  }
+
+  onChannelFormBuilt (form: FormGroup) {
+    this.formStepChannel = form
   }
 
   signup () {
     this.error = null
 
-    const userCreate: UserCreate = this.form.value
+    const body: UserRegister = Object.assign(this.formStepUser.value, this.formStepChannel.value)
 
-    this.userService.signup(userCreate).subscribe(
+    this.userService.signup(body).subscribe(
       () => {
         this.signupDone = true
 
         if (this.requiresEmailVerification) {
-          this.info = this.i18n('Welcome! Now please check your emails to verify your account and complete signup.')
+          this.info = this.i18n('Now please check your emails to verify your account and complete signup.')
           return
         }
 
         // Auto login
-        this.authService.login(userCreate.username, userCreate.password)
+        this.authService.login(body.username, body.password)
             .subscribe(
               () => {
-                this.notifier.success(this.i18n('You are now logged in as {{username}}!', { username: userCreate.username }))
-
-                this.redirectService.redirectToHomepage()
+                this.success = this.i18n('You are now logged in as {{username}}!', { username: body.username })
               },
 
               err => this.error = err.message
index 61560ddcfdf03b69205c8c7ae58c4d5f4aae698c..fccaf7ce19848044bfd34dd35540ba17a9d2cd4f 100644 (file)
@@ -1,17 +1,26 @@
 import { NgModule } from '@angular/core'
-
 import { SignupRoutingModule } from './signup-routing.module'
 import { SignupComponent } from './signup.component'
 import { SharedModule } from '../shared'
+import { CdkStepperModule } from '@angular/cdk/stepper'
+import { SignupStepChannelComponent } from '@app/signup/signup-step-channel.component'
+import { SignupStepUserComponent } from '@app/signup/signup-step-user.component'
+import { CustomStepperComponent } from '@app/signup/custom-stepper.component'
+import { SuccessComponent } from '@app/signup/success.component'
 
 @NgModule({
   imports: [
     SignupRoutingModule,
-    SharedModule
+    SharedModule,
+    CdkStepperModule
   ],
 
   declarations: [
-    SignupComponent
+    SignupComponent,
+    CustomStepperComponent,
+    SuccessComponent,
+    SignupStepChannelComponent,
+    SignupStepUserComponent
   ],
 
   exports: [
diff --git a/client/src/app/signup/success.component.html b/client/src/app/signup/success.component.html
new file mode 100644 (file)
index 0000000..68eb72b
--- /dev/null
@@ -0,0 +1,8 @@
+<!-- Thanks: Amit Singh Sansoya from https://codepen.io/amit3200/pen/zWMJOO -->
+
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 130.2 130.2">
+  <circle class="path circle" fill="none" stroke="#73AF55" stroke-width="6" stroke-miterlimit="10" cx="65.1" cy="65.1" r="62.1"/>
+  <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 "/>
+</svg>
+
+<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 (file)
index 0000000..7c66e08
--- /dev/null
@@ -0,0 +1,74 @@
+svg {
+  width: 100px;
+  display: block;
+  margin: 40px auto 0;
+}
+
+.path {
+  stroke-dasharray: 1000;
+  stroke-dashoffset: 0;
+
+  &.circle {
+    -webkit-animation: dash .9s ease-in-out;
+    animation: dash .9s ease-in-out;
+  }
+
+  &.line {
+    stroke-dashoffset: 1000;
+    -webkit-animation: dash .9s .35s ease-in-out forwards;
+    animation: dash .9s .35s ease-in-out forwards;
+  }
+
+  &.check {
+    stroke-dashoffset: -100;
+    -webkit-animation: dash-check .9s .35s ease-in-out forwards;
+    animation: dash-check .9s .35s ease-in-out forwards;
+  }
+}
+
+p {
+  text-align: center;
+  margin: 20px 0 60px;
+  font-size: 1.25em;
+
+  &.success {
+    color: #73AF55;
+  }
+}
+
+
+@-webkit-keyframes dash {
+  0% {
+    stroke-dashoffset: 1000;
+  }
+  100% {
+    stroke-dashoffset: 0;
+  }
+}
+
+@keyframes dash {
+  0% {
+    stroke-dashoffset: 1000;
+  }
+  100% {
+    stroke-dashoffset: 0;
+  }
+}
+
+@-webkit-keyframes dash-check {
+  0% {
+    stroke-dashoffset: -100;
+  }
+  100% {
+    stroke-dashoffset: 900;
+  }
+}
+
+@keyframes dash-check {
+  0% {
+    stroke-dashoffset: -100;
+  }
+  100% {
+    stroke-dashoffset: 900;
+  }
+}
diff --git a/client/src/app/signup/success.component.ts b/client/src/app/signup/success.component.ts
new file mode 100644 (file)
index 0000000..2674e1e
--- /dev/null
@@ -0,0 +1,10 @@
+import { Component } from '@angular/core'
+
+@Component({
+  selector: 'my-success',
+  templateUrl: './success.component.html',
+  styleUrls: [ './success.component.scss' ]
+})
+export class SuccessComponent {
+
+}
index 262a8136f6e0842552c4464ab9905159be363285..228a6116e8c517b91c01a1267da4704b57067be3 100644 (file)
 }
 
 @mixin peertube-checkbox ($border-width) {
-  display: none;
+  opacity: 0;
+  width: 0;
+
+  &:focus + span {
+    outline: auto;
+  }
 
   & + span {
     position: relative;
index b58dcc0d6adacc6b64c91b28c4ab529ea7f89761..7a081af33c266ceef1bec7551e9180997f14e912 100644 (file)
@@ -70,6 +70,12 @@ const usersRegisterValidator = [
           .end()
       }
 
+      if (body.channel.name === body.username) {
+        return res.status(400)
+                  .send({ error: 'Channel name cannot be the same than user username.' })
+                  .end()
+      }
+
       const existing = await ActorModel.loadLocalByName(body.channel.name)
       if (existing) {
         return res.status(409)
index d26032ea5139cf5472ed08a1b205d0e41aa95fa3..95097817b21dd5134370f353cded7f65f4b005a1 100644 (file)
@@ -737,6 +737,13 @@ describe('Test users API validators', function () {
       await makePostBodyRequest({ url: server.url, path: registrationPath, token: server.accessToken, fields })
     })
 
+    it('Should fail with a channel name that is the same than user username', async function () {
+      const source = { username: 'super_user', channel: { name: 'super_user', displayName: 'display name' } }
+      const fields = immutableAssign(baseCorrectParams, source)
+
+      await makePostBodyRequest({ url: server.url, path: registrationPath, token: server.accessToken, fields })
+    })
+
     it('Should fail with an existing channel', async function () {
       const videoChannelAttributesArg = { name: 'existing_channel', displayName: 'hello', description: 'super description' }
       await addVideoChannel(server.url, server.accessToken, videoChannelAttributesArg)