]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
improvements to login and sign-up pages (#3357)
authorKimsible <1877318+kimsible@users.noreply.github.com>
Mon, 7 Dec 2020 15:34:07 +0000 (16:34 +0100)
committerGitHub <noreply@github.com>
Mon, 7 Dec 2020 15:34:07 +0000 (16:34 +0100)
* New login form ui
* Move InstanceAboutAccordion to shared components
* Update closed registration alert text
* Add alert for opened registration and move them bellow login form
* Adjust flex block on signup and login views
* Replace toggle accordion with expand on links in signup and login + scrollTo
* Improve display of login alerts
* Fix missing Component suffix
* Define min-width instance-information block sign-up and login for mobile screens
* Add ability to select specific panels in instanceAboutAccorddion
* Add instance title and short-description to common instanceAboutAccordion
* Clarify title alert in login page
* Add step terms for signup

Co-authored-by: kimsible <kimsible@users.noreply.github.com>
Co-authored-by: Rigel Kent <sendmemail@rigelk.eu>
21 files changed:
client/src/app/+login/login.component.html
client/src/app/+login/login.component.scss
client/src/app/+login/login.component.ts
client/src/app/+login/login.module.ts
client/src/app/+signup/+register/custom-stepper.component.html
client/src/app/+signup/+register/custom-stepper.component.scss
client/src/app/+signup/+register/custom-stepper.component.ts
client/src/app/+signup/+register/register-step-terms.component.html [new file with mode: 0644]
client/src/app/+signup/+register/register-step-terms.component.ts [new file with mode: 0644]
client/src/app/+signup/+register/register-step-user.component.html
client/src/app/+signup/+register/register-step-user.component.ts
client/src/app/+signup/+register/register.component.html
client/src/app/+signup/+register/register.component.scss
client/src/app/+signup/+register/register.component.ts
client/src/app/+signup/+register/register.module.ts
client/src/app/shared/form-validators/user-validators.ts
client/src/app/shared/shared-instance/index.ts
client/src/app/shared/shared-instance/instance-about-accordion.component.html [new file with mode: 0644]
client/src/app/shared/shared-instance/instance-about-accordion.component.scss [new file with mode: 0644]
client/src/app/shared/shared-instance/instance-about-accordion.component.ts [new file with mode: 0644]
client/src/app/shared/shared-instance/shared-instance.module.ts

index 390d77dd7927be706025cca62ebc126037a832e2..a2362d9784cffcfc78b2ecc5a262b4d2f81e52cb 100644 (file)
@@ -8,73 +8,81 @@
   </div>
 
   <ng-container *ngIf="!externalAuthError && !isAuthenticatedWithExternalAuth">
-    <div class="looking-for-account alert alert-info" *ngIf="signupAllowed === false" role="alert">
-      <h6 class="alert-heading" i18n>
-        If you are looking for an account…
-      </h6>
-
-      <div i18n>
-        Currently this instance doesn't allow for user registration, but you can find an instance
-        that gives you the possibility to sign up for an account and upload your videos there.
-
-        <br />
-
-        Find yours among multiple instances at <a class="alert-link" href="https://joinpeertube.org/instances" target="_blank" rel="noopener noreferrer">https://joinpeertube.org/instances</a>.
-      </div>
-    </div>
-
     <div *ngIf="error" class="alert alert-danger">{{ error }}
       <span *ngIf="error === 'User email is not verified.'"> <a i18n routerLink="/verify-account/ask-send-email">Request new verification email.</a></span>
     </div>
 
-    <div class="login-form-and-externals">
-
-      <form role="form" (ngSubmit)="login()" [formGroup]="form">
-        <div class="form-group">
-          <div>
-            <label i18n for="username">User</label>
-            <input
-              type="text" id="username" i18n-placeholder placeholder="Username or email address" required tabindex="1"
-              formControlName="username" class="form-control" [ngClass]="{ 'input-error': formErrors['username'] }" #usernameInput
-            >
-            <a i18n *ngIf="signupAllowed === true" routerLink="/signup" class="create-an-account">
-              or create an account
-            </a>
+    <div class="wrapper">
+      <div class="login-form-and-externals">
+
+        <form role="form" (ngSubmit)="login()" [formGroup]="form">
+          <div class="form-group">
+            <div>
+              <label i18n for="username">User</label>
+              <input
+                type="text" id="username" i18n-placeholder placeholder="Username or email address" required tabindex="1"
+                formControlName="username" class="form-control" [ngClass]="{ 'input-error': formErrors['username'] }" #usernameInput
+              >
+            </div>
+
+            <div *ngIf="formErrors.username" class="form-error">
+              {{ formErrors.username }}
+            </div>
           </div>
 
-          <div *ngIf="formErrors.username" class="form-error">
-            {{ formErrors.username }}
-          </div>
-        </div>
-
-        <div class="form-group">
-          <label i18n for="password">Password</label>
-          <div>
+          <div class="form-group">
+            <label i18n for="password">Password</label>
             <my-input-toggle-hidden formControlName="password" id="password"
                                     i18n-placeholder placeholder="Password"
                                     [ngClass]="{ 'input-error': formErrors['password'] }"
-                                    autocomplete="current-password"></my-input-toggle-hidden>
-            <a i18n-title class="forgot-password-button" (click)="openForgotPasswordModal()" title="Click here to reset your password">I forgot my password</a>
+                                    autocomplete="current-password" tabindex="2"></my-input-toggle-hidden>
+            <div *ngIf="formErrors.password" class="form-error">
+              {{ formErrors.password }}
+            </div>
           </div>
-          <div *ngIf="formErrors.password" class="form-error">
-            {{ formErrors.password }}
+
+          <input type="submit" i18n-value value="Login" [disabled]="!form.valid">
+          
+          <div class="additionnal-links">
+            <a class="forgot-password-button" (click)="openForgotPasswordModal()" i18n-title title="Click here to reset your password">I forgot my password</a>
+            <div *ngIf="signupAllowed" class="signup-link">
+              <span>·</span>
+              <a i18n routerLink="/signup" class="create-an-account">Create an account</a>
+            </div>
           </div>
-        </div>
 
-        <input type="submit" i18n-value value="Login" [disabled]="!form.valid">
-      </form>
+          <div class="looking-for-account alert alert-info"  role="alert">
+            <h6 class="alert-heading" i18n>
+              Logging into an account lets you publish content
+            </h6>
+
+            <div *ngIf="signupAllowed" i18n>
+              This instance allows registration. However, be careful to check the <a class="terms-anchor" (click)="onTermsClick($event, instanceInformation)" href='#'>Terms</a><a class="terms-link" target="_blank" routerLink="/about/instance" fragment="terms">Terms</a> before creating an account.
+              You may also search for another instance to match your exact needs at: <br /><a class="alert-link" href="https://joinpeertube.org/instances" target="_blank" rel="noopener noreferrer">https://joinpeertube.org/instances</a>.
+            </div>
+
+            <div *ngIf="!signupAllowed" i18n>
+              Currently this instance doesn't allow for user registration, you may check the <a (click)="onTermsClick($event, instanceInformation)" href='#'>Terms</a> for more details or find an instance that gives you the possibility to sign up for an account and upload your videos there.
+              Find yours among multiple instances at: <br /> <a class="alert-link" href="https://joinpeertube.org/instances" target="_blank" rel="noopener noreferrer">https://joinpeertube.org/instances</a>.
+            </div>
+          </div>
+        </form>
 
-      <div class="external-login-blocks" *ngIf="getExternalLogins().length !== 0">
-        <div class="block-title" i18n>Or sign in with</div>
+        <div class="external-login-blocks" *ngIf="getExternalLogins().length !== 0">
+          <div class="block-title" i18n>Or sign in with</div>
 
-        <div>
-          <a class="external-login-block" *ngFor="let auth of getExternalLogins()" [href]="getAuthHref(auth)" role="button">
-            {{ auth.authDisplayName }}
-          </a>
+          <div>
+            <a class="external-login-block" *ngFor="let auth of getExternalLogins()" [href]="getAuthHref(auth)" role="button">
+              {{ auth.authDisplayName }}
+            </a>
+          </div>
         </div>
       </div>
-    </div>
 
+      <div #instanceInformation class="instance-information">
+        <my-instance-about-accordion (init)="onInstanceAboutAccordionInit($event)" [panels]="instanceInformationPanels"></my-instance-about-accordion>
+      </div>
+    </div>
   </ng-container>
 </div>
 
index 9ba09e9e446f4ba0da0d136ae7786b72d201cd74..3cc302aec4aa988c7584c3fd02280aa71d32d3f2 100644 (file)
@@ -1,5 +1,8 @@
 @import '_variables';
 @import '_mixins';
+@import './_bootstrap-variables';
+@import '~bootstrap/scss/functions';
+@import '~bootstrap/scss/variables';
 
 label {
   display: block;
@@ -57,39 +60,138 @@ input[type=submit] {
   }
 }
 
-.login-form-and-externals {
+.wrapper {
   display: flex;
+  justify-content: space-around;
   flex-wrap: wrap;
-  font-size: 15px;
 
-  form {
-    margin: 0 50px 20px 0;
+  & > div {
+    flex: 1 1;
   }
 
-  .external-login-blocks {
-    min-width: 200px;
+  .login-form-and-externals {
+    display: flex;
+    flex-wrap: wrap;
+    font-size: 15px;
+    max-width: 450px;
+    margin-bottom: 40px;
+    margin-left: 10px;
+    margin-right: 10px;
 
-    .block-title {
-      font-weight: $font-semibold;
+    form {
+      margin: 0;
+
+      &, input {
+        width: 100%;
+      }
+
+      .additionnal-links {
+        display: block;
+        text-align: center;
+        margin-top: 20px;
+        margin-bottom: 20px;
+
+        .forgot-password-button,
+        .create-an-account {
+          padding: 4px;
+          display: inline-block;
+
+          color: var(--mainColor);
+
+          &:hover, &:active {
+            color: var(--mainHoverColor);
+          }
+        }
+      }
     }
 
-    .external-login-block {
-      @include disable-default-a-behaviour;
-
-      cursor: pointer;
-      border: 1px solid #d1d7e0;
-      border-radius: 5px;
-      color: pvar(--mainForegroundColor);
-      margin: 10px 10px 0 0;
-      display: flex;
-      justify-content: center;
-      align-items: center;
-      min-height: 35px;
-      min-width: 100px;
-
-      &:hover {
-        background-color: rgba(209, 215, 224, 0.5)
+    .external-login-blocks {
+      min-width: 200px;
+
+      .block-title {
+        font-weight: $font-semibold;
+      }
+
+      .external-login-block {
+        @include disable-default-a-behaviour;
+
+        cursor: pointer;
+        border: 1px solid #d1d7e0;
+        border-radius: 5px;
+        color: pvar(--mainForegroundColor);
+        margin: 10px 10px 0 0;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        min-height: 35px;
+        min-width: 100px;
+
+        &:hover {
+          background-color: rgba(209, 215, 224, 0.5)
+        }
       }
     }
+
+    .signup-link {
+      display: inline-block;
+    }
+  }
+
+  .instance-information {
+    max-width: 600px;
+    min-width: 350px;
+    margin-bottom: 40px;
+    margin-left: 10px;
+    margin-right: 10px;
+  }
+
+  .terms-anchor {
+    display: inline;
+  }
+
+  .terms-link {
+    display: none;
+  }
+}
+
+@mixin columnReverseDisplay {
+  flex-direction: column-reverse;
+
+  .login-form-and-externals,
+  .instance-information {
+    width: 100%;
+    margin-left: 0;
+    margin-right: 0;
+    max-width: 450px;
+    min-width: unset;
+    align-self: center;
+  }
+
+  .instance-information {
+    ::ng-deep .accordion {
+      display: none;
+    }
+  }
+
+  .terms-anchor {
+    display: none;
+  }
+
+  .terms-link {
+    display: inline;
+  }
+}
+
+@media screen and (max-width: breakpoint(md)) {
+  .wrapper {
+    @include columnReverseDisplay();
+  }
+}
+
+@media screen and (max-width: breakpoint(md) + $menu-width) {
+  :host-context(.main-col:not(.expanded)) {
+    .wrapper {
+      @include columnReverseDisplay();
+    }
   }
 }
index 351750453285c2927966714b61f73ea62c756cc2..2567f21f152186d9e34e22ae763f30e8cdc6ce9c 100644 (file)
@@ -3,9 +3,10 @@ import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angula
 import { ActivatedRoute } from '@angular/router'
 import { AuthService, Notifier, RedirectService, UserService } from '@app/core'
 import { HooksService } from '@app/core/plugins/hooks.service'
+import { InstanceAboutAccordionComponent } from '@app/shared/shared-instance'
 import { LOGIN_PASSWORD_VALIDATOR, LOGIN_USERNAME_VALIDATOR } from '@app/shared/form-validators/login-validators'
 import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
-import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
+import { NgbAccordion, NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
 import { RegisteredExternalAuthConfig, ServerConfig } from '@shared/models'
 
 @Component({
@@ -18,6 +19,7 @@ export class LoginComponent extends FormReactive implements OnInit, AfterViewIni
   @ViewChild('usernameInput', { static: false }) usernameInput: ElementRef
   @ViewChild('forgotPasswordModal', { static: true }) forgotPasswordModal: ElementRef
 
+  accordion: NgbAccordion
   error: string = null
   forgotPasswordEmail = ''
 
@@ -25,6 +27,14 @@ export class LoginComponent extends FormReactive implements OnInit, AfterViewIni
   externalAuthError = false
   externalLogins: string[] = []
 
+  instanceInformationPanels = {
+    terms: true,
+    administrators: false,
+    features: false,
+    moderation: false,
+    codeOfConduct: false
+  }
+
   private openedForgotPasswordModal: NgbModalRef
   private serverConfig: ServerConfig
 
@@ -45,6 +55,15 @@ export class LoginComponent extends FormReactive implements OnInit, AfterViewIni
     return this.serverConfig.signup.allowed === true
   }
 
+  onTermsClick (event: Event, instanceInformation: HTMLElement) {
+    event.preventDefault()
+
+    if (this.accordion) {
+      this.accordion.expand('terms')
+      instanceInformation.scrollIntoView({ behavior: 'smooth' })
+    }
+  }
+
   isEmailDisabled () {
     return this.serverConfig.email.enabled === false
   }
@@ -122,6 +141,10 @@ The link will expire within 1 hour.`
     this.openedForgotPasswordModal.close()
   }
 
+  onInstanceAboutAccordionInit (instanceAboutAccordion: InstanceAboutAccordionComponent) {
+    this.accordion = instanceAboutAccordion.accordion
+  }
+
   private loadExternalAuthToken (username: string, token: string) {
     this.isAuthenticatedWithExternalAuth = true
 
index c419024267d1921b98b63aba8476086bba984512..9d8607e5d85e6a4d7e23616350a9f16a43a09c41 100644 (file)
@@ -1,6 +1,7 @@
 import { NgModule } from '@angular/core'
 import { SharedFormModule } from '@app/shared/shared-forms'
 import { SharedGlobalIconModule } from '@app/shared/shared-icons'
+import { SharedInstanceModule } from '@app/shared/shared-instance'
 import { SharedMainModule } from '@app/shared/shared-main'
 import { LoginRoutingModule } from './login-routing.module'
 import { LoginComponent } from './login.component'
@@ -11,7 +12,9 @@ import { LoginComponent } from './login.component'
 
     SharedMainModule,
     SharedFormModule,
-    SharedGlobalIconModule
+    SharedGlobalIconModule,
+
+    SharedInstanceModule
   ],
 
   declarations: [
index 4cfe54152d5826993d6cfc1cbd348b5a0fd5a72c..aad2f31d3daa629cb52eccd64794c2d7183b2bc5 100644 (file)
@@ -2,7 +2,7 @@
   <header *ngIf="steps.length > 2">
     <ng-container *ngFor="let step of steps; let i = index; let isLast = last;">
       <div
-        class="step-info" [ngClass]="{ active: selectedIndex === i, completed: isCompleted(step) }" [attr.aria-current]="selectedIndex === i"
+        class="step-info" [ngClass]="{ active: selectedIndex === i, completed: isCompleted(step), 'c-hand': isAccessible(i) }" [attr.aria-current]="selectedIndex === i"
         (click)="onClick(i)"
       >
         <div class="step-index">
index cc1591ee8e332a75cdad904549ca03b3547c4c29..3b4791d08bda5f6a0348eb224bc1ff51669e6465 100644 (file)
@@ -4,6 +4,12 @@
 $grey-color: #9CA3AB;
 $index-block-height: 32px;
 
+.container {
+  padding-left: 0;
+  padding-right: 0;
+  max-width: unset !important;
+}
+
 header {
   display: flex;
   justify-content: space-between;
@@ -17,6 +23,10 @@ header {
     align-items: center;
     width: $index-block-height;
 
+    &:not(.c-hand) {
+      cursor: default;
+    }
+
     .step-index {
       display: flex;
       justify-content: center;
index 2ae40f3a9696a9b8d4f928f4b1ad32c2b377797e..5a80895f9a81b99aa0f7d845735c04c33a68bc00 100644 (file)
@@ -16,4 +16,11 @@ export class CustomStepperComponent extends CdkStepper {
   isCompleted (step: CdkStep) {
     return step.stepControl && step.stepControl.dirty && step.stepControl.valid
   }
+
+  isAccessible (index: number) {
+    const stepsCompletedMap = this.steps.map(step => this.isCompleted(step))
+    return index === 0
+      ? true
+      : stepsCompletedMap[ index - 1 ]
+  }
 }
diff --git a/client/src/app/+signup/+register/register-step-terms.component.html b/client/src/app/+signup/+register/register-step-terms.component.html
new file mode 100644 (file)
index 0000000..1cfdc0a
--- /dev/null
@@ -0,0 +1,18 @@
+<form role="form" [formGroup]="form">
+  <div class="form-group form-group-terms">
+    <my-peertube-checkbox inputName="terms" formControlName="terms">
+      <ng-template ptTemplate="label">
+        <ng-container i18n>
+          I am at least 16 years old and agree
+          to the <a class="terms-anchor" (click)="onTermsClick($event)" href='#'>Terms</a>
+          <ng-container *ngIf="hasCodeOfConduct"> and to the <a (click)="onCodeOfConductClick($event)" href='#'>Code of Conduct</a></ng-container>
+          of this instance
+        </ng-container>
+      </ng-template>
+    </my-peertube-checkbox>
+
+    <div *ngIf="formErrors.terms" class="form-error">
+      {{ formErrors.terms }}
+    </div>
+  </div>
+</form>
diff --git a/client/src/app/+signup/+register/register-step-terms.component.ts b/client/src/app/+signup/+register/register-step-terms.component.ts
new file mode 100644 (file)
index 0000000..db834c6
--- /dev/null
@@ -0,0 +1,47 @@
+import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
+import { FormGroup } from '@angular/forms'
+import {
+  USER_TERMS_VALIDATOR
+} from '@app/shared/form-validators/user-validators'
+import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
+
+@Component({
+  selector: 'my-register-step-terms',
+  templateUrl: './register-step-terms.component.html',
+  styleUrls: [ './register.component.scss' ]
+})
+export class RegisterStepTermsComponent extends FormReactive implements OnInit {
+  @Input() hasCodeOfConduct = false
+
+  @Output() formBuilt = new EventEmitter<FormGroup>()
+  @Output() termsClick = new EventEmitter<void>()
+  @Output() codeOfConductClick = new EventEmitter<void>()
+
+  constructor (
+    protected formValidatorService: FormValidatorService
+  ) {
+    super()
+  }
+
+  get instanceHost () {
+    return window.location.host
+  }
+
+  ngOnInit () {
+    this.buildForm({
+      terms: USER_TERMS_VALIDATOR
+    })
+
+    setTimeout(() => this.formBuilt.emit(this.form))
+  }
+
+  onTermsClick (event: Event) {
+    event.preventDefault()
+    this.termsClick.emit()
+  }
+
+  onCodeOfConductClick (event: Event) {
+    event.preventDefault()
+    this.codeOfConductClick.emit()
+  }
+}
index 6eab4df0c8ea65fe459a46f343d9f9617c6329d6..ac341666bf741c6ab575dd27f9b30efa6bb68832 100644 (file)
     </div>
   </div>
 
-  <div class="form-group form-group-terms">
-    <my-peertube-checkbox inputName="terms" formControlName="terms">
-      <ng-template ptTemplate="label">
-        <ng-container i18n>
-          I am at least 16 years old and agree
-          to the <a (click)="onTermsClick($event)" href='#'>Terms</a>
-          <ng-container *ngIf="hasCodeOfConduct"> and to the <a (click)="onCodeOfConductClick($event)" href='#'>Code of Conduct</a></ng-container>
-          of this instance
-        </ng-container>
-      </ng-template>
-    </my-peertube-checkbox>
-
-    <div *ngIf="formErrors.terms" class="form-error">
-      {{ formErrors.terms }}
-    </div>
-  </div>
 </form>
index 9193540b4e2e446e360706c84b940361ada2d1bf..716cd8c7883a0b093239e05adafd0d734e11bfb2 100644 (file)
@@ -7,7 +7,6 @@ import {
   USER_DISPLAY_NAME_REQUIRED_VALIDATOR,
   USER_EMAIL_VALIDATOR,
   USER_PASSWORD_VALIDATOR,
-  USER_TERMS_VALIDATOR,
   USER_USERNAME_VALIDATOR
 } from '@app/shared/form-validators/user-validators'
 import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
@@ -18,12 +17,9 @@ import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
   styleUrls: [ './register.component.scss' ]
 })
 export class RegisterStepUserComponent extends FormReactive implements OnInit {
-  @Input() hasCodeOfConduct = false
   @Input() videoUploadDisabled = false
 
   @Output() formBuilt = new EventEmitter<FormGroup>()
-  @Output() termsClick = new EventEmitter<void>()
-  @Output() codeOfConductClick = new EventEmitter<void>()
 
   constructor (
     protected formValidatorService: FormValidatorService,
@@ -41,8 +37,7 @@ export class RegisterStepUserComponent extends FormReactive implements OnInit {
       displayName: USER_DISPLAY_NAME_REQUIRED_VALIDATOR,
       username: USER_USERNAME_VALIDATOR,
       password: USER_PASSWORD_VALIDATOR,
-      email: USER_EMAIL_VALIDATOR,
-      terms: USER_TERMS_VALIDATOR
+      email: USER_EMAIL_VALIDATOR
     })
 
     setTimeout(() => this.formBuilt.emit(this.form))
@@ -54,16 +49,6 @@ export class RegisterStepUserComponent extends FormReactive implements OnInit {
      .subscribe(([ oldValue, newValue ]) => this.onDisplayNameChange(oldValue, newValue))
   }
 
-  onTermsClick (event: Event) {
-    event.preventDefault()
-    this.termsClick.emit()
-  }
-
-  onCodeOfConductClick (event: Event) {
-    event.preventDefault()
-    this.codeOfConductClick.emit()
-  }
-
   private onDisplayNameChange (oldDisplayName: string, newDisplayName: string) {
     const username = this.form.value['username'] || ''
 
index 7e3704124d370853676b9bd71760cd85ebbc2af5..bebc5d9268042f7d4a8e7fc598f0059f4e15548d 100644 (file)
   <div class="wrapper" [hidden]="signupDone">
     <div class="register-form">
       <my-custom-stepper linear *ngIf="!signupDone">
-        <cdk-step [stepControl]="formStepUser" i18n-label label="User">
-          <my-register-step-user
+        <cdk-step [stepControl]="formStepTerms" i18n-label="Stepper label for the registration page describing terms of service" label="Terms">
+          <div class="instance-information">
+            <my-instance-about-accordion (init)="onInstanceAboutAccordionInit($event)" [panels]="instanceInformationPanels"></my-instance-about-accordion>
+          </div>
+
+          <my-register-step-terms
             [hasCodeOfConduct]="!!aboutHtml.codeOfConduct"
-            [videoUploadDisabled]="videoUploadDisabled"
-            (formBuilt)="onUserFormBuilt($event)" (termsClick)="onTermsClick()" (codeOfConductClick)="onCodeOfConductClick()"
-          >
-          </my-register-step-user>
+            (formBuilt)="onTermsFormBuilt($event)" (termsClick)="onTermsClick()" (codeOfConductClick)="onCodeOfConductClick()"
+          ></my-register-step-terms>
+
+          <button cdkStepperNext [disabled]="!formStepTerms || !formStepTerms.valid">{{ defaultNextStepButtonLabel }}</button>
+        </cdk-step>
+
+        <cdk-step [stepControl]="formStepUser" i18n-label="Stepper label for the registration page asking user informations" label="User">
+          <my-register-step-user (formBuilt)="onUserFormBuilt($event)" [videoUploadDisabled]="videoUploadDisabled"></my-register-step-user>
 
-          <button i18n cdkStepperNext [disabled]="!formStepUser || !formStepUser.valid"
-            (click)="signup()">{{ videoUploadDisabled ? 'Signup' : 'Next' }}</button>
+          <button cdkStepperNext [disabled]="!formStepUser || !formStepUser.valid" (click)="videoUploadDisabled && signup()">{{ stepUserButtonLabel }}</button>
         </cdk-step>
 
-        <cdk-step [stepControl]="formStepChannel" i18n-label label="Channel" *ngIf="!videoUploadDisabled">
+        <cdk-step [stepControl]="formStepChannel" i18n-label="Stepper label for the registration page asking information about the default channel" label="Channel" *ngIf="!videoUploadDisabled">
           <my-register-step-channel (formBuilt)="onChannelFormBuilt($event)" [username]="getUsername()"></my-register-step-channel>
 
           <button i18n cdkStepperNext (click)="signup()"
         </cdk-step>
       </my-custom-stepper>
     </div>
-
-    <div class="instance-information">
-      <ngb-accordion [closeOthers]="true" #accordion="ngbAccordion">
-        <ngb-panel id="instance-features" i18n-title title="Features found on this instance">
-          <ng-template ngbPanelContent>
-            <my-instance-features-table></my-instance-features-table>
-          </ng-template>
-        </ngb-panel>
-
-        <ng-container *ngIf="about">
-          <ngb-panel
-            *ngIf="aboutHtml.administrator || about.instance.maintenanceLifetime || about.instance.businessModel"
-            id="admin-sustainability" i18n-title title="Administrators & Sustainability"
-          >
-            <ng-template ngbPanelContent>
-              <div class="block">
-                <strong i18n>Who are we?</strong>
-                <div [innerHTML]="aboutHtml.administrator"></div>
-              </div>
-
-              <div class="block">
-                <strong i18n>How long do we plan to maintain this instance?</strong>
-                <div [innerHTML]="about.instance.maintenanceLifetime"></div>
-              </div>
-
-              <div class="block">
-                <strong i18n>How will we finance this instance?</strong>
-                <div [innerHTML]="about.instance.businessModel"></div>
-              </div>
-            </ng-template>
-          </ngb-panel>
-
-          <ngb-panel *ngIf="aboutHtml.moderationInformation" id="moderation-information" i18n-title title="Moderation information">
-            <ng-template ngbPanelContent>
-              <div class="block" [innerHTML]="aboutHtml.moderationInformation"></div>
-            </ng-template>
-          </ngb-panel>
-
-          <ngb-panel *ngIf="aboutHtml.codeOfConduct" id="code-of-conduct" i18n-title title="Code of conduct">
-            <ng-template ngbPanelContent>
-              <div class="block" [innerHTML]="aboutHtml.codeOfConduct"></div>
-            </ng-template>
-          </ngb-panel>
-
-          <ngb-panel *ngIf="aboutHtml.terms" id="terms" i18n-title title="Terms">
-            <ng-template ngbPanelContent>
-              <div class="block" [innerHTML]="aboutHtml.terms"></div>
-            </ng-template>
-          </ngb-panel>
-        </ng-container>
-      </ngb-accordion>
-    </div>
   </div>
 
 </div>
index 4221354ee621f7fd90f973999035be9639587536..d22d58c4a4f598644d35b7bdb134d3dcd6d5bd5b 100644 (file)
@@ -1,9 +1,5 @@
 @import '_variables';
 @import '_mixins';
-@import "./_bootstrap-variables";
-
-@import '~bootstrap/scss/functions';
-@import '~bootstrap/scss/variables';
 
 .alert {
   font-size: 15px;
 
 .wrapper {
   display: flex;
-  justify-content: space-between;
-  flex-wrap: wrap;
-
-  & > div {
-    margin-bottom: 40px;
+  flex-direction: column;
 
-    &.register-form {
-      width: 450px;
+  .register-form {
+    max-width: 600px;
+    align-self: center;
+  }
 
-      @media screen and (max-width: $mobile-view) {
-        width: 100%;
-      }
-    }
+  .register-form,
+  .instance-information {
+    width: 100%;
+  }
 
-    &.instance-information {
-      width: 600px;
-      margin-bottom: 40px;
-
-      .block {
-        font-size: 15px;
-        margin-bottom: 15px;
-        padding: 0 $btn-padding-x;
-      }
-
-      @media screen and (max-width: 1500px) {
-        width: 450px;
-      }
-      @media screen and (max-width: $mobile-view) {
-        width: 100%;
-      }
-
-      ngb-accordion ::ng-deep {
-        .btn {
-          font-weight: $font-semibold !important;
-          color: pvar(--mainForegroundColor) !important;
-        }
-      }
-    }
+  .instance-information {
+    margin-bottom: 15px;
   }
 }
 
 }
 
 .input-group {
-  @include peertube-input-group(400px);
+  @include peertube-input-group(100%);
 }
 
 .input-group-append {
   height: 30px;
 }
 
+.form-group-terms {
+  width: 100% !important;
+}
+
 input:not([type=submit]) {
-  @include peertube-input-text(400px);
+  @include peertube-input-text(100%);
   display: block;
 
   &#username,
@@ -76,19 +52,10 @@ input:not([type=submit]) {
   }
 }
 
-@media screen and (max-width: $mobile-view) {
-  .form-group-terms,
-  .input-group,
-  input:not([type=submit]) {
-    width: 100%;
-  }
-}
-
 input[type=submit],
 button {
   @include peertube-button;
   @include orange-button;
-  
 }
 
 .name-information {
index 5b6762631dcb7074866a02179fecc2b4d283f3b5..b1f5d8e93f2d16c0b3bad474039140b6ecc16f68 100644 (file)
@@ -1,12 +1,12 @@
-import { Component, OnInit, ViewChild } from '@angular/core'
+import { Component, OnInit } from '@angular/core'
 import { FormGroup } from '@angular/forms'
 import { ActivatedRoute } from '@angular/router'
-import { AuthService, Notifier, UserService } from '@app/core'
+import { AuthService, UserService } from '@app/core'
 import { HooksService } from '@app/core/plugins/hooks.service'
-import { InstanceService } from '@app/shared/shared-instance'
 import { NgbAccordion } from '@ng-bootstrap/ng-bootstrap'
 import { UserRegister } from '@shared/models'
-import { About, ServerConfig } from '@shared/models/server'
+import { ServerConfig } from '@shared/models/server'
+import { InstanceAboutAccordionComponent } from '@app/shared/shared-instance'
 
 @Component({
   selector: 'my-register',
@@ -14,35 +14,39 @@ import { About, ServerConfig } from '@shared/models/server'
   styleUrls: [ './register.component.scss' ]
 })
 export class RegisterComponent implements OnInit {
-  @ViewChild('accordion', { static: true }) accordion: NgbAccordion
-
+  accordion: NgbAccordion
   info: string = null
   error: string = null
   success: string = null
   signupDone = false
 
-  about: About
-  aboutHtml = {
-    description: '',
-    terms: '',
-    codeOfConduct: '',
-    moderationInformation: '',
-    administrator: ''
-  }
-
   videoUploadDisabled: boolean
 
+  formStepTerms: FormGroup
   formStepUser: FormGroup
   formStepChannel: FormGroup
 
+  aboutHtml = {
+    codeOfConduct: ''
+  }
+
+  instanceInformationPanels = {
+    codeOfConduct: true,
+    terms: true,
+    administrators: false,
+    features: false,
+    moderation: false
+  }
+
+  defaultNextStepButtonLabel = $localize`Next`
+  stepUserButtonLabel = this.defaultNextStepButtonLabel
+
   private serverConfig: ServerConfig
 
   constructor (
     private route: ActivatedRoute,
     private authService: AuthService,
-    private notifier: Notifier,
     private userService: UserService,
-    private instanceService: InstanceService,
     private hooks: HooksService
     ) {
   }
@@ -55,19 +59,12 @@ export class RegisterComponent implements OnInit {
     this.serverConfig = this.route.snapshot.data.serverConfig
 
     this.videoUploadDisabled = this.serverConfig.user.videoQuota === 0
-
-    this.instanceService.getAbout()
-      .subscribe(
-        async about => {
-          this.about = about
-
-          this.aboutHtml = await this.instanceService.buildHtml(about)
-        },
-
-        err => this.notifier.error(err.message)
-      )
+    this.stepUserButtonLabel = this.videoUploadDisabled
+      ? $localize`Signup`
+      : this.defaultNextStepButtonLabel
 
     this.hooks.runAction('action:signup.register.init', 'signup')
+
   }
 
   hasSameChannelAndAccountNames () {
@@ -86,6 +83,10 @@ export class RegisterComponent implements OnInit {
     return this.formStepChannel.value['name']
   }
 
+  onTermsFormBuilt (form: FormGroup) {
+    this.formStepTerms = form
+  }
+
   onUserFormBuilt (form: FormGroup) {
     this.formStepUser = form
   }
@@ -102,6 +103,11 @@ export class RegisterComponent implements OnInit {
     if (this.accordion) this.accordion.toggle('code-of-conduct')
   }
 
+  onInstanceAboutAccordionInit (instanceAboutAccordion: InstanceAboutAccordionComponent) {
+    this.accordion = instanceAboutAccordion.accordion
+    this.aboutHtml = instanceAboutAccordion.aboutHtml
+  }
+
   async signup () {
     this.error = null
 
index 608045f58f39273cfbff95662bb478765e6b8c4c..c36da53d57c5bd885741f6a49d4165f86013131c 100644 (file)
@@ -2,10 +2,10 @@ import { CdkStepperModule } from '@angular/cdk/stepper'
 import { NgModule } from '@angular/core'
 import { SignupSharedModule } from '@app/+signup/shared/signup-shared.module'
 import { SharedInstanceModule } from '@app/shared/shared-instance'
-import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'
 import { CustomStepperComponent } from './custom-stepper.component'
 import { RegisterRoutingModule } from './register-routing.module'
 import { RegisterStepChannelComponent } from './register-step-channel.component'
+import { RegisterStepTermsComponent } from './register-step-terms.component'
 import { RegisterStepUserComponent } from './register-step-user.component'
 import { RegisterComponent } from './register.component'
 
@@ -14,7 +14,6 @@ import { RegisterComponent } from './register.component'
     RegisterRoutingModule,
 
     CdkStepperModule,
-    NgbAccordionModule,
 
     SignupSharedModule,
 
@@ -25,6 +24,7 @@ import { RegisterComponent } from './register.component'
     RegisterComponent,
     CustomStepperComponent,
     RegisterStepChannelComponent,
+    RegisterStepTermsComponent,
     RegisterStepUserComponent
   ],
 
index 115930146a88a6db98aa3ba044b113d16959c5e1..a272900358889eddeb73c2a10dba271d441b63b9 100644 (file)
@@ -115,9 +115,7 @@ export const USER_DESCRIPTION_VALIDATOR: BuildFormValidator = {
 }
 
 export const USER_TERMS_VALIDATOR: BuildFormValidator = {
-  VALIDATORS: [
-    Validators.requiredTrue
-  ],
+  VALIDATORS: [ Validators.requiredTrue ],
   MESSAGES: {
     'required': $localize`You must agree with the instance terms in order to register on it.`
   }
index 1aeed357eb05d8812441311e32b142e7a9319edb..d7172f7b6641355539075df607c395b5299d8a4d 100644 (file)
@@ -1,4 +1,5 @@
 export * from './feature-boolean.component'
+export * from './instance-about-accordion.component'
 export * from './instance-features-table.component'
 export * from './instance-follow.service'
 export * from './instance-statistics.component'
diff --git a/client/src/app/shared/shared-instance/instance-about-accordion.component.html b/client/src/app/shared/shared-instance/instance-about-accordion.component.html
new file mode 100644 (file)
index 0000000..e91e446
--- /dev/null
@@ -0,0 +1,53 @@
+<h2 class="instance-name">{{ about?.instance.name }}</h2>
+
+<div class="instance-short-description">{{ about?.instance.shortDescription }}</div>
+
+<ngb-accordion #accordion="ngbAccordion" [closeOthers]="true">
+  <ngb-panel *ngIf="panels.features" id="instance-features" i18n-title title="Features found on this instance">
+    <ng-template ngbPanelContent>
+      <my-instance-features-table></my-instance-features-table>
+    </ng-template>
+  </ngb-panel>
+
+  <ng-container *ngIf="about">
+    <ngb-panel
+      *ngIf="getAdministratorsPanel()"
+      id="admin-sustainability" i18n-title title="Administrators & Sustainability"
+    >
+      <ng-template ngbPanelContent>
+        <div class="block">
+          <strong i18n>Who are we?</strong>
+          <div [innerHTML]="aboutHtml.administrator"></div>
+        </div>
+
+        <div class="block">
+          <strong i18n>How long do we plan to maintain this instance?</strong>
+          <div [innerHTML]="about.instance.maintenanceLifetime"></div>
+        </div>
+
+        <div class="block">
+          <strong i18n>How will we finance this instance?</strong>
+          <div [innerHTML]="about.instance.businessModel"></div>
+        </div>
+      </ng-template>
+    </ngb-panel>
+
+    <ngb-panel *ngIf="termsPanel" id="terms" i18n-title title="Terms">
+      <ng-template ngbPanelContent>
+        <div class="block" [innerHTML]="aboutHtml.terms"></div>
+      </ng-template>
+    </ngb-panel>
+
+    <ngb-panel *ngIf="moderationPanel" id="moderation-information" i18n-title title="Moderation information">
+      <ng-template ngbPanelContent>
+        <div class="block" [innerHTML]="aboutHtml.moderationInformation"></div>
+      </ng-template>
+    </ngb-panel>
+
+    <ngb-panel *ngIf="codeOfConductPanel" id="code-of-conduct" i18n-title title="Code of conduct">
+      <ng-template ngbPanelContent>
+        <div class="block" [innerHTML]="aboutHtml.codeOfConduct"></div>
+      </ng-template>
+    </ngb-panel>
+  </ng-container>
+</ngb-accordion>
diff --git a/client/src/app/shared/shared-instance/instance-about-accordion.component.scss b/client/src/app/shared/shared-instance/instance-about-accordion.component.scss
new file mode 100644 (file)
index 0000000..275600d
--- /dev/null
@@ -0,0 +1,46 @@
+@import '_variables';
+@import '_mixins';
+@import "./_bootstrap-variables";
+
+@import '~bootstrap/scss/functions';
+@import '~bootstrap/scss/variables';
+
+.instance-name {
+  line-height: 1.7rem;
+}
+
+.instance-short-description {
+  @include ellipsis-multiline(1rem, 3);
+
+  margin-top: 20px;
+  margin-bottom: 20px;
+}
+
+.block {
+  font-size: 15px;
+  margin-bottom: 15px;
+  padding: 0 $btn-padding-x;
+}
+
+ngb-accordion ::ng-deep {
+  .card {
+    border-color: var(--mainBackgroundColor);
+
+    .card-header {
+      background-color: unset;
+      padding: 0;
+
+      & + .collapse.show {
+        background-color: var(--submenuColor);
+      }
+    }
+  }
+
+  .btn {
+    @include peertube-button;
+    @include grey-button;
+
+    border-radius: unset;
+    width: 100%;
+  }
+}
diff --git a/client/src/app/shared/shared-instance/instance-about-accordion.component.ts b/client/src/app/shared/shared-instance/instance-about-accordion.component.ts
new file mode 100644 (file)
index 0000000..ab75c31
--- /dev/null
@@ -0,0 +1,71 @@
+import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'
+import { NgbAccordion } from '@ng-bootstrap/ng-bootstrap'
+import { InstanceService } from './instance.service'
+import { Notifier } from '@app/core'
+import { About } from '@shared/models/server'
+
+@Component({
+  selector: 'my-instance-about-accordion',
+  templateUrl: './instance-about-accordion.component.html',
+  styleUrls: ['./instance-about-accordion.component.scss']
+})
+export class InstanceAboutAccordionComponent implements OnInit {
+  @ViewChild('accordion', { static: true }) accordion: NgbAccordion
+  @Output() init: EventEmitter<InstanceAboutAccordionComponent> = new EventEmitter<InstanceAboutAccordionComponent>()
+
+  @Input() panels = {
+    features: true,
+    administrators: true,
+    moderation: true,
+    codeOfConduct: true,
+    terms: true
+  }
+
+  about: About
+  aboutHtml = {
+    description: '',
+    terms: '',
+    codeOfConduct: '',
+    moderationInformation: '',
+    administrator: ''
+  }
+
+  constructor (
+    private instanceService: InstanceService,
+    private notifier: Notifier
+  ) { }
+
+  ngOnInit (): void {
+    this.instanceService.getAbout()
+      .subscribe(
+        async about => {
+          this.about = about
+
+          this.aboutHtml = await this.instanceService.buildHtml(about)
+
+          this.init.emit(this)
+        },
+
+        err => this.notifier.error(err.message)
+      )
+  }
+
+  getAdministratorsPanel () {
+    if (!this.about) return false
+    if (!this.panels.administrators) return false
+
+    return !!(this.aboutHtml?.administrator || this.about?.instance.maintenanceLifetime || this.about?.instance.businessModel)
+  }
+
+  get moderationPanel () {
+    return this.panels.moderation && !!this.aboutHtml.moderationInformation
+  }
+
+  get codeOfConductPanel () {
+    return this.panels.codeOfConduct && !!this.aboutHtml.codeOfConduct
+  }
+
+  get termsPanel () {
+    return this.panels.terms && !!this.aboutHtml.terms
+  }
+}
index b75ad1a127e9677ab0f3f30f1bfbf8f41b52fba5..13c681ab8fabadd2e9d592e22cd99672de00d8d7 100644 (file)
@@ -1,7 +1,9 @@
 
 import { NgModule } from '@angular/core'
+import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'
 import { SharedMainModule } from '../shared-main/shared-main.module'
 import { FeatureBooleanComponent } from './feature-boolean.component'
+import { InstanceAboutAccordionComponent } from './instance-about-accordion.component'
 import { InstanceFeaturesTableComponent } from './instance-features-table.component'
 import { InstanceFollowService } from './instance-follow.service'
 import { InstanceStatisticsComponent } from './instance-statistics.component'
@@ -9,17 +11,20 @@ import { InstanceService } from './instance.service'
 
 @NgModule({
   imports: [
-    SharedMainModule
+    SharedMainModule,
+    NgbAccordionModule
   ],
 
   declarations: [
     FeatureBooleanComponent,
+    InstanceAboutAccordionComponent,
     InstanceFeaturesTableComponent,
     InstanceStatisticsComponent
   ],
 
   exports: [
     FeatureBooleanComponent,
+    InstanceAboutAccordionComponent,
     InstanceFeaturesTableComponent,
     InstanceStatisticsComponent
   ],