aboutsummaryrefslogtreecommitdiffhomepage
path: root/client
diff options
context:
space:
mode:
Diffstat (limited to 'client')
-rw-r--r--client/src/app/app.module.ts14
-rw-r--r--client/src/app/login/login.component.html44
-rw-r--r--client/src/app/login/login.component.scss10
-rw-r--r--client/src/app/login/login.component.ts37
-rw-r--r--client/src/app/reset-password/index.ts3
-rw-r--r--client/src/app/reset-password/reset-password-routing.module.ts25
-rw-r--r--client/src/app/reset-password/reset-password.component.html33
-rw-r--r--client/src/app/reset-password/reset-password.component.scss12
-rw-r--r--client/src/app/reset-password/reset-password.component.ts79
-rw-r--r--client/src/app/reset-password/reset-password.module.ts24
-rw-r--r--client/src/app/shared/users/user.service.ts21
-rw-r--r--client/src/polyfills.ts34
-rw-r--r--client/src/sass/application.scss2
13 files changed, 309 insertions, 29 deletions
diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts
index e69edbc4b..ddcaf3f48 100644
--- a/client/src/app/app.module.ts
+++ b/client/src/app/app.module.ts
@@ -1,19 +1,20 @@
1import { NgModule } from '@angular/core' 1import { NgModule } from '@angular/core'
2import { BrowserModule } from '@angular/platform-browser' 2import { BrowserModule } from '@angular/platform-browser'
3import { ResetPasswordModule } from '@app/reset-password'
3 4
4import { MetaModule, MetaLoader, MetaStaticLoader, PageTitlePositioning } from '@ngx-meta/core' 5import { MetaLoader, MetaModule, MetaStaticLoader, PageTitlePositioning } from '@ngx-meta/core'
6
7import { AccountModule } from './account'
5 8
6import { AppRoutingModule } from './app-routing.module' 9import { AppRoutingModule } from './app-routing.module'
7import { AppComponent } from './app.component' 10import { AppComponent } from './app.component'
8
9import { AccountModule } from './account'
10import { CoreModule } from './core' 11import { CoreModule } from './core'
12import { HeaderComponent } from './header'
11import { LoginModule } from './login' 13import { LoginModule } from './login'
12import { SignupModule } from './signup' 14import { MenuComponent } from './menu'
13import { SharedModule } from './shared' 15import { SharedModule } from './shared'
16import { SignupModule } from './signup'
14import { VideosModule } from './videos' 17import { VideosModule } from './videos'
15import { MenuComponent } from './menu'
16import { HeaderComponent } from './header'
17 18
18export function metaFactory (): MetaLoader { 19export function metaFactory (): MetaLoader {
19 return new MetaStaticLoader({ 20 return new MetaStaticLoader({
@@ -46,6 +47,7 @@ export function metaFactory (): MetaLoader {
46 AccountModule, 47 AccountModule,
47 CoreModule, 48 CoreModule,
48 LoginModule, 49 LoginModule,
50 ResetPasswordModule,
49 SignupModule, 51 SignupModule,
50 SharedModule, 52 SharedModule,
51 VideosModule, 53 VideosModule,
diff --git a/client/src/app/login/login.component.html b/client/src/app/login/login.component.html
index b61b66ec7..660a08280 100644
--- a/client/src/app/login/login.component.html
+++ b/client/src/app/login/login.component.html
@@ -19,10 +19,13 @@
19 19
20 <div class="form-group"> 20 <div class="form-group">
21 <label for="password">Password</label> 21 <label for="password">Password</label>
22 <input 22 <div>
23 type="password" name="password" id="password" placeholder="Password" required 23 <input
24 formControlName="password" [ngClass]="{ 'input-error': formErrors['password'] }" 24 type="password" name="password" id="password" placeholder="Password" required
25 > 25 formControlName="password" [ngClass]="{ 'input-error': formErrors['password'] }"
26 >
27 <div class="forgot-password-button" (click)="openForgotPasswordModal()">I forgot my password</div>
28 </div>
26 <div *ngIf="formErrors.password" class="form-error"> 29 <div *ngIf="formErrors.password" class="form-error">
27 {{ formErrors.password }} 30 {{ formErrors.password }}
28 </div> 31 </div>
@@ -31,3 +34,36 @@
31 <input type="submit" value="Login" [disabled]="!form.valid"> 34 <input type="submit" value="Login" [disabled]="!form.valid">
32 </form> 35 </form>
33</div> 36</div>
37
38<div bsModal #forgotPasswordModal="bs-modal" (onShown)="onForgotPasswordModalShown()" class="modal" tabindex="-1">
39 <div class="modal-dialog">
40 <div class="modal-content">
41
42 <div class="modal-header">
43 <span class="close" aria-hidden="true" (click)="hideForgotPasswordModal()"></span>
44 <h4 class="modal-title">Forgot your password</h4>
45 </div>
46
47 <div class="modal-body">
48 <div class="form-group">
49 <label for="forgot-password-email">Email</label>
50 <input
51 type="email" id="forgot-password-email" placeholder="Email address" required
52 [(ngModel)]="forgotPasswordEmail" #forgotPasswordEmailInput
53 >
54 </div>
55
56 <div class="form-group inputs">
57 <span class="action-button action-button-cancel" (click)="hideForgotPasswordModal()">
58 Cancel
59 </span>
60
61 <input
62 type="submit" value="Send me an email to reset my password" class="action-button-submit"
63 (click)="askResetPassword()" [disabled]="!forgotPasswordEmailInput.validity.valid"
64 >
65 </div>
66 </div>
67 </div>
68 </div>
69</div>
diff --git a/client/src/app/login/login.component.scss b/client/src/app/login/login.component.scss
index efec6b706..2cf6991ce 100644
--- a/client/src/app/login/login.component.scss
+++ b/client/src/app/login/login.component.scss
@@ -10,3 +10,13 @@ input[type=submit] {
10 @include peertube-button; 10 @include peertube-button;
11 @include orange-button; 11 @include orange-button;
12} 12}
13
14input[type=password] {
15 display: inline-block;
16 margin-right: 5px;
17}
18
19.forgot-password-button {
20 display: inline-block;
21 cursor: pointer;
22}
diff --git a/client/src/app/login/login.component.ts b/client/src/app/login/login.component.ts
index e7c9c7226..22e8c77dd 100644
--- a/client/src/app/login/login.component.ts
+++ b/client/src/app/login/login.component.ts
@@ -1,7 +1,9 @@
1import { Component, OnInit } from '@angular/core' 1import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'
2import { FormBuilder, FormGroup, Validators } from '@angular/forms' 2import { FormBuilder, FormGroup, Validators } from '@angular/forms'
3import { Router } from '@angular/router' 3import { Router } from '@angular/router'
4 4import { UserService } from '@app/shared'
5import { NotificationsService } from 'angular2-notifications'
6import { ModalDirective } from 'ngx-bootstrap/modal'
5import { AuthService } from '../core' 7import { AuthService } from '../core'
6import { FormReactive } from '../shared' 8import { FormReactive } from '../shared'
7 9
@@ -12,6 +14,9 @@ import { FormReactive } from '../shared'
12}) 14})
13 15
14export class LoginComponent extends FormReactive implements OnInit { 16export class LoginComponent extends FormReactive implements OnInit {
17 @ViewChild('forgotPasswordModal') forgotPasswordModal: ModalDirective
18 @ViewChild('forgotPasswordEmailInput') forgotPasswordEmailInput: ElementRef
19
15 error: string = null 20 error: string = null
16 21
17 form: FormGroup 22 form: FormGroup
@@ -27,9 +32,12 @@ export class LoginComponent extends FormReactive implements OnInit {
27 'required': 'Password is required.' 32 'required': 'Password is required.'
28 } 33 }
29 } 34 }
35 forgotPasswordEmail = ''
30 36
31 constructor ( 37 constructor (
32 private authService: AuthService, 38 private authService: AuthService,
39 private userService: UserService,
40 private notificationsService: NotificationsService,
33 private formBuilder: FormBuilder, 41 private formBuilder: FormBuilder,
34 private router: Router 42 private router: Router
35 ) { 43 ) {
@@ -60,4 +68,29 @@ export class LoginComponent extends FormReactive implements OnInit {
60 err => this.error = err.message 68 err => this.error = err.message
61 ) 69 )
62 } 70 }
71
72 askResetPassword () {
73 this.userService.askResetPassword(this.forgotPasswordEmail)
74 .subscribe(
75 res => {
76 const message = `An email with the reset password instructions will be sent to ${this.forgotPasswordEmail}.`
77 this.notificationsService.success('Success', message)
78 this.hideForgotPasswordModal()
79 },
80
81 err => this.notificationsService.error('Error', err.message)
82 )
83 }
84
85 onForgotPasswordModalShown () {
86 this.forgotPasswordEmailInput.nativeElement.focus()
87 }
88
89 openForgotPasswordModal () {
90 this.forgotPasswordModal.show()
91 }
92
93 hideForgotPasswordModal () {
94 this.forgotPasswordModal.hide()
95 }
63} 96}
diff --git a/client/src/app/reset-password/index.ts b/client/src/app/reset-password/index.ts
new file mode 100644
index 000000000..438dc576a
--- /dev/null
+++ b/client/src/app/reset-password/index.ts
@@ -0,0 +1,3 @@
1export * from './reset-password-routing.module'
2export * from './reset-password.component'
3export * from './reset-password.module'
diff --git a/client/src/app/reset-password/reset-password-routing.module.ts b/client/src/app/reset-password/reset-password-routing.module.ts
new file mode 100644
index 000000000..b41069568
--- /dev/null
+++ b/client/src/app/reset-password/reset-password-routing.module.ts
@@ -0,0 +1,25 @@
1import { NgModule } from '@angular/core'
2import { RouterModule, Routes } from '@angular/router'
3
4import { MetaGuard } from '@ngx-meta/core'
5
6import { ResetPasswordComponent } from './reset-password.component'
7
8const resetPasswordRoutes: Routes = [
9 {
10 path: 'reset-password',
11 component: ResetPasswordComponent,
12 canActivate: [ MetaGuard ],
13 data: {
14 meta: {
15 title: 'Reset password'
16 }
17 }
18 }
19]
20
21@NgModule({
22 imports: [ RouterModule.forChild(resetPasswordRoutes) ],
23 exports: [ RouterModule ]
24})
25export class ResetPasswordRoutingModule {}
diff --git a/client/src/app/reset-password/reset-password.component.html b/client/src/app/reset-password/reset-password.component.html
new file mode 100644
index 000000000..d142c523f
--- /dev/null
+++ b/client/src/app/reset-password/reset-password.component.html
@@ -0,0 +1,33 @@
1<div class="margin-content">
2 <div class="title-page title-page-single">
3 Reset my password
4 </div>
5
6 <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
7
8 <form role="form" (ngSubmit)="resetPassword()" [formGroup]="form">
9 <div class="form-group">
10 <label for="password">Password</label>
11 <input
12 type="password" name="password" id="password" placeholder="Password" required
13 formControlName="password" [ngClass]="{ 'input-error': formErrors['password'] }"
14 >
15 <div *ngIf="formErrors.password" class="form-error">
16 {{ formErrors.password }}
17 </div>
18 </div>
19
20 <div class="form-group">
21 <label for="password-confirm">Confirm password</label>
22 <input
23 type="password" name="password-confirm" id="password-confirm" placeholder="Confirmed password" required
24 formControlName="password-confirm" [ngClass]="{ 'input-error': formErrors['password-confirm'] }"
25 >
26 <div *ngIf="formErrors['password-confirm']" class="form-error">
27 {{ formErrors['password-confirm'] }}
28 </div>
29 </div>
30
31 <input type="submit" value="Reset my password" [disabled]="!form.valid && isConfirmedPasswordValid()">
32 </form>
33</div>
diff --git a/client/src/app/reset-password/reset-password.component.scss b/client/src/app/reset-password/reset-password.component.scss
new file mode 100644
index 000000000..efec6b706
--- /dev/null
+++ b/client/src/app/reset-password/reset-password.component.scss
@@ -0,0 +1,12 @@
1@import '_variables';
2@import '_mixins';
3
4input:not([type=submit]) {
5 @include peertube-input-text(340px);
6 display: block;
7}
8
9input[type=submit] {
10 @include peertube-button;
11 @include orange-button;
12}
diff --git a/client/src/app/reset-password/reset-password.component.ts b/client/src/app/reset-password/reset-password.component.ts
new file mode 100644
index 000000000..408374779
--- /dev/null
+++ b/client/src/app/reset-password/reset-password.component.ts
@@ -0,0 +1,79 @@
1import { Component, OnInit } from '@angular/core'
2import { FormBuilder, FormGroup, Validators } from '@angular/forms'
3import { ActivatedRoute, Router } from '@angular/router'
4import { USER_PASSWORD, UserService } from '@app/shared'
5import { NotificationsService } from 'angular2-notifications'
6import { AuthService } from '../core'
7import { FormReactive } from '../shared'
8
9@Component({
10 selector: 'my-login',
11 templateUrl: './reset-password.component.html',
12 styleUrls: [ './reset-password.component.scss' ]
13})
14
15export class ResetPasswordComponent extends FormReactive implements OnInit {
16 form: FormGroup
17 formErrors = {
18 'password': '',
19 'password-confirm': ''
20 }
21 validationMessages = {
22 'password': USER_PASSWORD.MESSAGES,
23 'password-confirm': {
24 'required': 'Confirmation of the password is required.'
25 }
26 }
27
28 private userId: number
29 private verificationString: string
30
31 constructor (
32 private authService: AuthService,
33 private userService: UserService,
34 private notificationsService: NotificationsService,
35 private formBuilder: FormBuilder,
36 private router: Router,
37 private route: ActivatedRoute
38 ) {
39 super()
40 }
41
42 buildForm () {
43 this.form = this.formBuilder.group({
44 password: [ '', USER_PASSWORD.VALIDATORS ],
45 'password-confirm': [ '', Validators.required ]
46 })
47
48 this.form.valueChanges.subscribe(data => this.onValueChanged(data))
49 }
50
51 ngOnInit () {
52 this.buildForm()
53
54 this.userId = this.route.snapshot.queryParams['userId']
55 this.verificationString = this.route.snapshot.queryParams['verificationString']
56
57 if (!this.userId || !this.verificationString) {
58 this.notificationsService.error('Error', 'Unable to find user id or verification string.')
59 this.router.navigate([ '/' ])
60 }
61 }
62
63 resetPassword () {
64 this.userService.resetPassword(this.userId, this.verificationString, this.form.value.password)
65 .subscribe(
66 () => {
67 this.notificationsService.success('Success', 'Your password has been successfully reset!')
68 this.router.navigate([ '/login' ])
69 },
70
71 err => this.notificationsService.error('Error', err.message)
72 )
73 }
74
75 isConfirmedPasswordValid () {
76 const values = this.form.value
77 return values.password === values['password-confirm']
78 }
79}
diff --git a/client/src/app/reset-password/reset-password.module.ts b/client/src/app/reset-password/reset-password.module.ts
new file mode 100644
index 000000000..c2711981a
--- /dev/null
+++ b/client/src/app/reset-password/reset-password.module.ts
@@ -0,0 +1,24 @@
1import { NgModule } from '@angular/core'
2
3import { ResetPasswordRoutingModule } from './reset-password-routing.module'
4import { ResetPasswordComponent } from './reset-password.component'
5import { SharedModule } from '../shared'
6
7@NgModule({
8 imports: [
9 ResetPasswordRoutingModule,
10 SharedModule
11 ],
12
13 declarations: [
14 ResetPasswordComponent
15 ],
16
17 exports: [
18 ResetPasswordComponent
19 ],
20
21 providers: [
22 ]
23})
24export class ResetPasswordModule { }
diff --git a/client/src/app/shared/users/user.service.ts b/client/src/app/shared/users/user.service.ts
index 742fb0728..da7b583f4 100644
--- a/client/src/app/shared/users/user.service.ts
+++ b/client/src/app/shared/users/user.service.ts
@@ -5,7 +5,6 @@ import 'rxjs/add/operator/map'
5import { UserCreate, UserUpdateMe } from '../../../../../shared' 5import { UserCreate, UserUpdateMe } from '../../../../../shared'
6import { environment } from '../../../environments/environment' 6import { environment } from '../../../environments/environment'
7import { RestExtractor } from '../rest' 7import { RestExtractor } from '../rest'
8import { User } from './user.model'
9 8
10@Injectable() 9@Injectable()
11export class UserService { 10export class UserService {
@@ -54,4 +53,24 @@ export class UserService {
54 return this.authHttp.get(url) 53 return this.authHttp.get(url)
55 .catch(res => this.restExtractor.handleError(res)) 54 .catch(res => this.restExtractor.handleError(res))
56 } 55 }
56
57 askResetPassword (email: string) {
58 const url = UserService.BASE_USERS_URL + '/ask-reset-password'
59
60 return this.authHttp.post(url, { email })
61 .map(this.restExtractor.extractDataBool)
62 .catch(res => this.restExtractor.handleError(res))
63 }
64
65 resetPassword (userId: number, verificationString: string, password: string) {
66 const url = `${UserService.BASE_USERS_URL}/${userId}/reset-password`
67 const body = {
68 verificationString,
69 password
70 }
71
72 return this.authHttp.post(url, body)
73 .map(this.restExtractor.extractDataBool)
74 .catch(res => this.restExtractor.handleError(res))
75 }
57} 76}
diff --git a/client/src/polyfills.ts b/client/src/polyfills.ts
index c2d7f1d6e..fbe104aa0 100644
--- a/client/src/polyfills.ts
+++ b/client/src/polyfills.ts
@@ -19,26 +19,30 @@
19 */ 19 */
20 20
21/** IE9, IE10 and IE11 requires all of the following polyfills. **/ 21/** IE9, IE10 and IE11 requires all of the following polyfills. **/
22// import 'core-js/es6/symbol'; 22
23// import 'core-js/es6/object'; 23// For Google Bot
24// import 'core-js/es6/function'; 24import 'core-js/es6/symbol';
25// import 'core-js/es6/parse-int'; 25import 'core-js/es6/object';
26// import 'core-js/es6/parse-float'; 26import 'core-js/es6/function';
27// import 'core-js/es6/number'; 27import 'core-js/es6/parse-int';
28// import 'core-js/es6/math'; 28import 'core-js/es6/parse-float';
29// import 'core-js/es6/string'; 29import 'core-js/es6/number';
30// import 'core-js/es6/date'; 30import 'core-js/es6/math';
31// import 'core-js/es6/array'; 31import 'core-js/es6/string';
32// import 'core-js/es6/regexp'; 32import 'core-js/es6/date';
33// import 'core-js/es6/map'; 33import 'core-js/es6/array';
34// import 'core-js/es6/weak-map'; 34import 'core-js/es6/regexp';
35// import 'core-js/es6/set'; 35import 'core-js/es6/map';
36import 'core-js/es6/weak-map';
37import 'core-js/es6/set';
36 38
37/** IE10 and IE11 requires the following for NgClass support on SVG elements */ 39/** IE10 and IE11 requires the following for NgClass support on SVG elements */
38// import 'classlist.js'; // Run `npm install --save classlist.js`. 40// import 'classlist.js'; // Run `npm install --save classlist.js`.
39 41
40/** IE10 and IE11 requires the following for the Reflect API. */ 42/** IE10 and IE11 requires the following for the Reflect API. */
41// import 'core-js/es6/reflect'; 43
44// For Google Bot
45import 'core-js/es6/reflect';
42 46
43 47
44/** Evergreen browsers require these. **/ 48/** Evergreen browsers require these. **/
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index 253bb1b3c..33d7ce0a5 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -19,7 +19,7 @@ $FontPathSourceSansPro: '../../node_modules/npm-font-source-sans-pro/fonts';
19} 19}
20 20
21body { 21body {
22 font-family: 'Source Sans Pro'; 22 font-family: 'Source Sans Pro', sans-serif;
23 font-weight: $font-regular; 23 font-weight: $font-regular;
24 color: #000; 24 color: #000;
25} 25}