diff options
author | Kimsible <1877318+kimsible@users.noreply.github.com> | 2020-12-07 16:34:07 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-12-07 16:34:07 +0100 |
commit | 40360c17d82b33accb34ea974c275e17880c37aa (patch) | |
tree | e0ddac1174e54897b4871daa4dca43dce121f590 /client/src/app/+login | |
parent | 10f26f4203b8cef32778bf3435d8112eaea3c093 (diff) | |
download | PeerTube-40360c17d82b33accb34ea974c275e17880c37aa.tar.gz PeerTube-40360c17d82b33accb34ea974c275e17880c37aa.tar.zst PeerTube-40360c17d82b33accb34ea974c275e17880c37aa.zip |
improvements to login and sign-up pages (#3357)
* 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>
Diffstat (limited to 'client/src/app/+login')
-rw-r--r-- | client/src/app/+login/login.component.html | 108 | ||||
-rw-r--r-- | client/src/app/+login/login.component.scss | 150 | ||||
-rw-r--r-- | client/src/app/+login/login.component.ts | 25 | ||||
-rw-r--r-- | client/src/app/+login/login.module.ts | 5 |
4 files changed, 212 insertions, 76 deletions
diff --git a/client/src/app/+login/login.component.html b/client/src/app/+login/login.component.html index 390d77dd7..a2362d978 100644 --- a/client/src/app/+login/login.component.html +++ b/client/src/app/+login/login.component.html | |||
@@ -8,73 +8,81 @@ | |||
8 | </div> | 8 | </div> |
9 | 9 | ||
10 | <ng-container *ngIf="!externalAuthError && !isAuthenticatedWithExternalAuth"> | 10 | <ng-container *ngIf="!externalAuthError && !isAuthenticatedWithExternalAuth"> |
11 | <div class="looking-for-account alert alert-info" *ngIf="signupAllowed === false" role="alert"> | ||
12 | <h6 class="alert-heading" i18n> | ||
13 | If you are looking for an account… | ||
14 | </h6> | ||
15 | |||
16 | <div i18n> | ||
17 | Currently this instance doesn't allow for user registration, but you can find an instance | ||
18 | that gives you the possibility to sign up for an account and upload your videos there. | ||
19 | |||
20 | <br /> | ||
21 | |||
22 | 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>. | ||
23 | </div> | ||
24 | </div> | ||
25 | |||
26 | <div *ngIf="error" class="alert alert-danger">{{ error }} | 11 | <div *ngIf="error" class="alert alert-danger">{{ error }} |
27 | <span *ngIf="error === 'User email is not verified.'"> <a i18n routerLink="/verify-account/ask-send-email">Request new verification email.</a></span> | 12 | <span *ngIf="error === 'User email is not verified.'"> <a i18n routerLink="/verify-account/ask-send-email">Request new verification email.</a></span> |
28 | </div> | 13 | </div> |
29 | 14 | ||
30 | <div class="login-form-and-externals"> | 15 | <div class="wrapper"> |
31 | 16 | <div class="login-form-and-externals"> | |
32 | <form role="form" (ngSubmit)="login()" [formGroup]="form"> | 17 | |
33 | <div class="form-group"> | 18 | <form role="form" (ngSubmit)="login()" [formGroup]="form"> |
34 | <div> | 19 | <div class="form-group"> |
35 | <label i18n for="username">User</label> | 20 | <div> |
36 | <input | 21 | <label i18n for="username">User</label> |
37 | type="text" id="username" i18n-placeholder placeholder="Username or email address" required tabindex="1" | 22 | <input |
38 | formControlName="username" class="form-control" [ngClass]="{ 'input-error': formErrors['username'] }" #usernameInput | 23 | type="text" id="username" i18n-placeholder placeholder="Username or email address" required tabindex="1" |
39 | > | 24 | formControlName="username" class="form-control" [ngClass]="{ 'input-error': formErrors['username'] }" #usernameInput |
40 | <a i18n *ngIf="signupAllowed === true" routerLink="/signup" class="create-an-account"> | 25 | > |
41 | or create an account | 26 | </div> |
42 | </a> | 27 | |
28 | <div *ngIf="formErrors.username" class="form-error"> | ||
29 | {{ formErrors.username }} | ||
30 | </div> | ||
43 | </div> | 31 | </div> |
44 | 32 | ||
45 | <div *ngIf="formErrors.username" class="form-error"> | 33 | <div class="form-group"> |
46 | {{ formErrors.username }} | 34 | <label i18n for="password">Password</label> |
47 | </div> | ||
48 | </div> | ||
49 | |||
50 | <div class="form-group"> | ||
51 | <label i18n for="password">Password</label> | ||
52 | <div> | ||
53 | <my-input-toggle-hidden formControlName="password" id="password" | 35 | <my-input-toggle-hidden formControlName="password" id="password" |
54 | i18n-placeholder placeholder="Password" | 36 | i18n-placeholder placeholder="Password" |
55 | [ngClass]="{ 'input-error': formErrors['password'] }" | 37 | [ngClass]="{ 'input-error': formErrors['password'] }" |
56 | autocomplete="current-password"></my-input-toggle-hidden> | 38 | autocomplete="current-password" tabindex="2"></my-input-toggle-hidden> |
57 | <a i18n-title class="forgot-password-button" (click)="openForgotPasswordModal()" title="Click here to reset your password">I forgot my password</a> | 39 | <div *ngIf="formErrors.password" class="form-error"> |
40 | {{ formErrors.password }} | ||
41 | </div> | ||
58 | </div> | 42 | </div> |
59 | <div *ngIf="formErrors.password" class="form-error"> | 43 | |
60 | {{ formErrors.password }} | 44 | <input type="submit" i18n-value value="Login" [disabled]="!form.valid"> |
45 | |||
46 | <div class="additionnal-links"> | ||
47 | <a class="forgot-password-button" (click)="openForgotPasswordModal()" i18n-title title="Click here to reset your password">I forgot my password</a> | ||
48 | <div *ngIf="signupAllowed" class="signup-link"> | ||
49 | <span>·</span> | ||
50 | <a i18n routerLink="/signup" class="create-an-account">Create an account</a> | ||
51 | </div> | ||
61 | </div> | 52 | </div> |
62 | </div> | ||
63 | 53 | ||
64 | <input type="submit" i18n-value value="Login" [disabled]="!form.valid"> | 54 | <div class="looking-for-account alert alert-info" role="alert"> |
65 | </form> | 55 | <h6 class="alert-heading" i18n> |
56 | Logging into an account lets you publish content | ||
57 | </h6> | ||
58 | |||
59 | <div *ngIf="signupAllowed" i18n> | ||
60 | 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. | ||
61 | 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>. | ||
62 | </div> | ||
63 | |||
64 | <div *ngIf="!signupAllowed" i18n> | ||
65 | 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. | ||
66 | 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>. | ||
67 | </div> | ||
68 | </div> | ||
69 | </form> | ||
66 | 70 | ||
67 | <div class="external-login-blocks" *ngIf="getExternalLogins().length !== 0"> | 71 | <div class="external-login-blocks" *ngIf="getExternalLogins().length !== 0"> |
68 | <div class="block-title" i18n>Or sign in with</div> | 72 | <div class="block-title" i18n>Or sign in with</div> |
69 | 73 | ||
70 | <div> | 74 | <div> |
71 | <a class="external-login-block" *ngFor="let auth of getExternalLogins()" [href]="getAuthHref(auth)" role="button"> | 75 | <a class="external-login-block" *ngFor="let auth of getExternalLogins()" [href]="getAuthHref(auth)" role="button"> |
72 | {{ auth.authDisplayName }} | 76 | {{ auth.authDisplayName }} |
73 | </a> | 77 | </a> |
78 | </div> | ||
74 | </div> | 79 | </div> |
75 | </div> | 80 | </div> |
76 | </div> | ||
77 | 81 | ||
82 | <div #instanceInformation class="instance-information"> | ||
83 | <my-instance-about-accordion (init)="onInstanceAboutAccordionInit($event)" [panels]="instanceInformationPanels"></my-instance-about-accordion> | ||
84 | </div> | ||
85 | </div> | ||
78 | </ng-container> | 86 | </ng-container> |
79 | </div> | 87 | </div> |
80 | 88 | ||
diff --git a/client/src/app/+login/login.component.scss b/client/src/app/+login/login.component.scss index 9ba09e9e4..3cc302aec 100644 --- a/client/src/app/+login/login.component.scss +++ b/client/src/app/+login/login.component.scss | |||
@@ -1,5 +1,8 @@ | |||
1 | @import '_variables'; | 1 | @import '_variables'; |
2 | @import '_mixins'; | 2 | @import '_mixins'; |
3 | @import './_bootstrap-variables'; | ||
4 | @import '~bootstrap/scss/functions'; | ||
5 | @import '~bootstrap/scss/variables'; | ||
3 | 6 | ||
4 | label { | 7 | label { |
5 | display: block; | 8 | display: block; |
@@ -57,39 +60,138 @@ input[type=submit] { | |||
57 | } | 60 | } |
58 | } | 61 | } |
59 | 62 | ||
60 | .login-form-and-externals { | 63 | .wrapper { |
61 | display: flex; | 64 | display: flex; |
65 | justify-content: space-around; | ||
62 | flex-wrap: wrap; | 66 | flex-wrap: wrap; |
63 | font-size: 15px; | ||
64 | 67 | ||
65 | form { | 68 | & > div { |
66 | margin: 0 50px 20px 0; | 69 | flex: 1 1; |
67 | } | 70 | } |
68 | 71 | ||
69 | .external-login-blocks { | 72 | .login-form-and-externals { |
70 | min-width: 200px; | 73 | display: flex; |
74 | flex-wrap: wrap; | ||
75 | font-size: 15px; | ||
76 | max-width: 450px; | ||
77 | margin-bottom: 40px; | ||
78 | margin-left: 10px; | ||
79 | margin-right: 10px; | ||
71 | 80 | ||
72 | .block-title { | 81 | form { |
73 | font-weight: $font-semibold; | 82 | margin: 0; |
83 | |||
84 | &, input { | ||
85 | width: 100%; | ||
86 | } | ||
87 | |||
88 | .additionnal-links { | ||
89 | display: block; | ||
90 | text-align: center; | ||
91 | margin-top: 20px; | ||
92 | margin-bottom: 20px; | ||
93 | |||
94 | .forgot-password-button, | ||
95 | .create-an-account { | ||
96 | padding: 4px; | ||
97 | display: inline-block; | ||
98 | |||
99 | color: var(--mainColor); | ||
100 | |||
101 | &:hover, &:active { | ||
102 | color: var(--mainHoverColor); | ||
103 | } | ||
104 | } | ||
105 | } | ||
74 | } | 106 | } |
75 | 107 | ||
76 | .external-login-block { | 108 | .external-login-blocks { |
77 | @include disable-default-a-behaviour; | 109 | min-width: 200px; |
78 | 110 | ||
79 | cursor: pointer; | 111 | .block-title { |
80 | border: 1px solid #d1d7e0; | 112 | font-weight: $font-semibold; |
81 | border-radius: 5px; | 113 | } |
82 | color: pvar(--mainForegroundColor); | 114 | |
83 | margin: 10px 10px 0 0; | 115 | .external-login-block { |
84 | display: flex; | 116 | @include disable-default-a-behaviour; |
85 | justify-content: center; | 117 | |
86 | align-items: center; | 118 | cursor: pointer; |
87 | min-height: 35px; | 119 | border: 1px solid #d1d7e0; |
88 | min-width: 100px; | 120 | border-radius: 5px; |
89 | 121 | color: pvar(--mainForegroundColor); | |
90 | &:hover { | 122 | margin: 10px 10px 0 0; |
91 | background-color: rgba(209, 215, 224, 0.5) | 123 | display: flex; |
124 | justify-content: center; | ||
125 | align-items: center; | ||
126 | min-height: 35px; | ||
127 | min-width: 100px; | ||
128 | |||
129 | &:hover { | ||
130 | background-color: rgba(209, 215, 224, 0.5) | ||
131 | } | ||
92 | } | 132 | } |
93 | } | 133 | } |
134 | |||
135 | .signup-link { | ||
136 | display: inline-block; | ||
137 | } | ||
138 | } | ||
139 | |||
140 | .instance-information { | ||
141 | max-width: 600px; | ||
142 | min-width: 350px; | ||
143 | margin-bottom: 40px; | ||
144 | margin-left: 10px; | ||
145 | margin-right: 10px; | ||
146 | } | ||
147 | |||
148 | .terms-anchor { | ||
149 | display: inline; | ||
150 | } | ||
151 | |||
152 | .terms-link { | ||
153 | display: none; | ||
154 | } | ||
155 | } | ||
156 | |||
157 | @mixin columnReverseDisplay { | ||
158 | flex-direction: column-reverse; | ||
159 | |||
160 | .login-form-and-externals, | ||
161 | .instance-information { | ||
162 | width: 100%; | ||
163 | margin-left: 0; | ||
164 | margin-right: 0; | ||
165 | max-width: 450px; | ||
166 | min-width: unset; | ||
167 | align-self: center; | ||
168 | } | ||
169 | |||
170 | .instance-information { | ||
171 | ::ng-deep .accordion { | ||
172 | display: none; | ||
173 | } | ||
174 | } | ||
175 | |||
176 | .terms-anchor { | ||
177 | display: none; | ||
178 | } | ||
179 | |||
180 | .terms-link { | ||
181 | display: inline; | ||
182 | } | ||
183 | } | ||
184 | |||
185 | @media screen and (max-width: breakpoint(md)) { | ||
186 | .wrapper { | ||
187 | @include columnReverseDisplay(); | ||
188 | } | ||
189 | } | ||
190 | |||
191 | @media screen and (max-width: breakpoint(md) + $menu-width) { | ||
192 | :host-context(.main-col:not(.expanded)) { | ||
193 | .wrapper { | ||
194 | @include columnReverseDisplay(); | ||
195 | } | ||
94 | } | 196 | } |
95 | } | 197 | } |
diff --git a/client/src/app/+login/login.component.ts b/client/src/app/+login/login.component.ts index 351750453..2567f21f1 100644 --- a/client/src/app/+login/login.component.ts +++ b/client/src/app/+login/login.component.ts | |||
@@ -3,9 +3,10 @@ import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angula | |||
3 | import { ActivatedRoute } from '@angular/router' | 3 | import { ActivatedRoute } from '@angular/router' |
4 | import { AuthService, Notifier, RedirectService, UserService } from '@app/core' | 4 | import { AuthService, Notifier, RedirectService, UserService } from '@app/core' |
5 | import { HooksService } from '@app/core/plugins/hooks.service' | 5 | import { HooksService } from '@app/core/plugins/hooks.service' |
6 | import { InstanceAboutAccordionComponent } from '@app/shared/shared-instance' | ||
6 | import { LOGIN_PASSWORD_VALIDATOR, LOGIN_USERNAME_VALIDATOR } from '@app/shared/form-validators/login-validators' | 7 | import { LOGIN_PASSWORD_VALIDATOR, LOGIN_USERNAME_VALIDATOR } from '@app/shared/form-validators/login-validators' |
7 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | 8 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' |
8 | import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' | 9 | import { NgbAccordion, NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' |
9 | import { RegisteredExternalAuthConfig, ServerConfig } from '@shared/models' | 10 | import { RegisteredExternalAuthConfig, ServerConfig } from '@shared/models' |
10 | 11 | ||
11 | @Component({ | 12 | @Component({ |
@@ -18,6 +19,7 @@ export class LoginComponent extends FormReactive implements OnInit, AfterViewIni | |||
18 | @ViewChild('usernameInput', { static: false }) usernameInput: ElementRef | 19 | @ViewChild('usernameInput', { static: false }) usernameInput: ElementRef |
19 | @ViewChild('forgotPasswordModal', { static: true }) forgotPasswordModal: ElementRef | 20 | @ViewChild('forgotPasswordModal', { static: true }) forgotPasswordModal: ElementRef |
20 | 21 | ||
22 | accordion: NgbAccordion | ||
21 | error: string = null | 23 | error: string = null |
22 | forgotPasswordEmail = '' | 24 | forgotPasswordEmail = '' |
23 | 25 | ||
@@ -25,6 +27,14 @@ export class LoginComponent extends FormReactive implements OnInit, AfterViewIni | |||
25 | externalAuthError = false | 27 | externalAuthError = false |
26 | externalLogins: string[] = [] | 28 | externalLogins: string[] = [] |
27 | 29 | ||
30 | instanceInformationPanels = { | ||
31 | terms: true, | ||
32 | administrators: false, | ||
33 | features: false, | ||
34 | moderation: false, | ||
35 | codeOfConduct: false | ||
36 | } | ||
37 | |||
28 | private openedForgotPasswordModal: NgbModalRef | 38 | private openedForgotPasswordModal: NgbModalRef |
29 | private serverConfig: ServerConfig | 39 | private serverConfig: ServerConfig |
30 | 40 | ||
@@ -45,6 +55,15 @@ export class LoginComponent extends FormReactive implements OnInit, AfterViewIni | |||
45 | return this.serverConfig.signup.allowed === true | 55 | return this.serverConfig.signup.allowed === true |
46 | } | 56 | } |
47 | 57 | ||
58 | onTermsClick (event: Event, instanceInformation: HTMLElement) { | ||
59 | event.preventDefault() | ||
60 | |||
61 | if (this.accordion) { | ||
62 | this.accordion.expand('terms') | ||
63 | instanceInformation.scrollIntoView({ behavior: 'smooth' }) | ||
64 | } | ||
65 | } | ||
66 | |||
48 | isEmailDisabled () { | 67 | isEmailDisabled () { |
49 | return this.serverConfig.email.enabled === false | 68 | return this.serverConfig.email.enabled === false |
50 | } | 69 | } |
@@ -122,6 +141,10 @@ The link will expire within 1 hour.` | |||
122 | this.openedForgotPasswordModal.close() | 141 | this.openedForgotPasswordModal.close() |
123 | } | 142 | } |
124 | 143 | ||
144 | onInstanceAboutAccordionInit (instanceAboutAccordion: InstanceAboutAccordionComponent) { | ||
145 | this.accordion = instanceAboutAccordion.accordion | ||
146 | } | ||
147 | |||
125 | private loadExternalAuthToken (username: string, token: string) { | 148 | private loadExternalAuthToken (username: string, token: string) { |
126 | this.isAuthenticatedWithExternalAuth = true | 149 | this.isAuthenticatedWithExternalAuth = true |
127 | 150 | ||
diff --git a/client/src/app/+login/login.module.ts b/client/src/app/+login/login.module.ts index c41902426..9d8607e5d 100644 --- a/client/src/app/+login/login.module.ts +++ b/client/src/app/+login/login.module.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import { NgModule } from '@angular/core' | 1 | import { NgModule } from '@angular/core' |
2 | import { SharedFormModule } from '@app/shared/shared-forms' | 2 | import { SharedFormModule } from '@app/shared/shared-forms' |
3 | import { SharedGlobalIconModule } from '@app/shared/shared-icons' | 3 | import { SharedGlobalIconModule } from '@app/shared/shared-icons' |
4 | import { SharedInstanceModule } from '@app/shared/shared-instance' | ||
4 | import { SharedMainModule } from '@app/shared/shared-main' | 5 | import { SharedMainModule } from '@app/shared/shared-main' |
5 | import { LoginRoutingModule } from './login-routing.module' | 6 | import { LoginRoutingModule } from './login-routing.module' |
6 | import { LoginComponent } from './login.component' | 7 | import { LoginComponent } from './login.component' |
@@ -11,7 +12,9 @@ import { LoginComponent } from './login.component' | |||
11 | 12 | ||
12 | SharedMainModule, | 13 | SharedMainModule, |
13 | SharedFormModule, | 14 | SharedFormModule, |
14 | SharedGlobalIconModule | 15 | SharedGlobalIconModule, |
16 | |||
17 | SharedInstanceModule | ||
15 | ], | 18 | ], |
16 | 19 | ||
17 | declarations: [ | 20 | declarations: [ |