diff options
13 files changed, 242 insertions, 83 deletions
diff --git a/client/src/app/account/account-change-password/account-change-password.component.html b/client/src/app/account/account-change-password/account-change-password.component.html new file mode 100644 index 000000000..92d9f900a --- /dev/null +++ b/client/src/app/account/account-change-password/account-change-password.component.html | |||
@@ -0,0 +1,24 @@ | |||
1 | <div *ngIf="error" class="alert alert-danger">{{ error }}</div> | ||
2 | |||
3 | <form role="form" (ngSubmit)="changePassword()" [formGroup]="form"> | ||
4 | <div class="form-group"> | ||
5 | <label for="new-password">New password</label> | ||
6 | <input | ||
7 | type="password" class="form-control" id="new-password" | ||
8 | formControlName="new-password" | ||
9 | > | ||
10 | <div *ngIf="formErrors['new-password']" class="alert alert-danger"> | ||
11 | {{ formErrors['new-password'] }} | ||
12 | </div> | ||
13 | </div> | ||
14 | |||
15 | <div class="form-group"> | ||
16 | <label for="name">Confirm new password</label> | ||
17 | <input | ||
18 | type="password" class="form-control" id="new-confirmed-password" | ||
19 | formControlName="new-confirmed-password" | ||
20 | > | ||
21 | </div> | ||
22 | |||
23 | <input type="submit" value="Change password" class="btn btn-default" [disabled]="!form.valid"> | ||
24 | </form> | ||
diff --git a/client/src/app/account/account-change-password/account-change-password.component.ts b/client/src/app/account/account-change-password/account-change-password.component.ts new file mode 100644 index 000000000..15dc42d22 --- /dev/null +++ b/client/src/app/account/account-change-password/account-change-password.component.ts | |||
@@ -0,0 +1,66 @@ | |||
1 | import { Component, OnInit } from '@angular/core'; | ||
2 | import { FormBuilder, FormGroup } from '@angular/forms'; | ||
3 | import { Router } from '@angular/router'; | ||
4 | |||
5 | import { NotificationsService } from 'angular2-notifications'; | ||
6 | |||
7 | import { FormReactive, UserService, USER_PASSWORD } from '../../shared'; | ||
8 | |||
9 | @Component({ | ||
10 | selector: 'my-account-change-password', | ||
11 | templateUrl: './account-change-password.component.html' | ||
12 | }) | ||
13 | |||
14 | export class AccountChangePasswordComponent extends FormReactive implements OnInit { | ||
15 | error: string = null; | ||
16 | |||
17 | form: FormGroup; | ||
18 | formErrors = { | ||
19 | 'new-password': '', | ||
20 | 'new-confirmed-password': '' | ||
21 | }; | ||
22 | validationMessages = { | ||
23 | 'new-password': USER_PASSWORD.MESSAGES, | ||
24 | 'new-confirmed-password': USER_PASSWORD.MESSAGES | ||
25 | }; | ||
26 | |||
27 | constructor( | ||
28 | private formBuilder: FormBuilder, | ||
29 | private router: Router, | ||
30 | private notificationsService: NotificationsService, | ||
31 | private userService: UserService | ||
32 | ) { | ||
33 | super(); | ||
34 | } | ||
35 | |||
36 | buildForm() { | ||
37 | this.form = this.formBuilder.group({ | ||
38 | 'new-password': [ '', USER_PASSWORD.VALIDATORS ], | ||
39 | 'new-confirmed-password': [ '', USER_PASSWORD.VALIDATORS ], | ||
40 | }); | ||
41 | |||
42 | this.form.valueChanges.subscribe(data => this.onValueChanged(data)); | ||
43 | } | ||
44 | |||
45 | ngOnInit() { | ||
46 | this.buildForm(); | ||
47 | } | ||
48 | |||
49 | changePassword() { | ||
50 | const newPassword = this.form.value['new-password']; | ||
51 | const newConfirmedPassword = this.form.value['new-confirmed-password']; | ||
52 | |||
53 | this.error = null; | ||
54 | |||
55 | if (newPassword !== newConfirmedPassword) { | ||
56 | this.error = 'The new password and the confirmed password do not correspond.'; | ||
57 | return; | ||
58 | } | ||
59 | |||
60 | this.userService.changePassword(newPassword).subscribe( | ||
61 | () => this.notificationsService.success('Success', 'Password updated.'), | ||
62 | |||
63 | err => this.error = err | ||
64 | ); | ||
65 | } | ||
66 | } | ||
diff --git a/client/src/app/account/account-change-password/index.ts b/client/src/app/account/account-change-password/index.ts new file mode 100644 index 000000000..72a63e48d --- /dev/null +++ b/client/src/app/account/account-change-password/index.ts | |||
@@ -0,0 +1 @@ | |||
export * from './account-change-password.component'; | |||
diff --git a/client/src/app/account/account-details/account-details.component.html b/client/src/app/account/account-details/account-details.component.html new file mode 100644 index 000000000..24b0750d2 --- /dev/null +++ b/client/src/app/account/account-details/account-details.component.html | |||
@@ -0,0 +1,16 @@ | |||
1 | <div *ngIf="error" class="alert alert-danger">{{ error }}</div> | ||
2 | |||
3 | <form role="form" (ngSubmit)="updateDetails()" [formGroup]="form"> | ||
4 | <div class="form-group"> | ||
5 | <label for="displayNSFW">Display NSFW videos</label> | ||
6 | <input | ||
7 | type="checkbox" id="displayNSFW" | ||
8 | formControlName="displayNSFW" | ||
9 | > | ||
10 | <div *ngIf="formErrors['displayNSFW']" class="alert alert-danger"> | ||
11 | {{ formErrors['displayNSFW'] }} | ||
12 | </div> | ||
13 | </div> | ||
14 | |||
15 | <input type="submit" value="Update" class="btn btn-default" [disabled]="!form.valid"> | ||
16 | </form> | ||
diff --git a/client/src/app/account/account-details/account-details.component.ts b/client/src/app/account/account-details/account-details.component.ts new file mode 100644 index 000000000..30e5b14ee --- /dev/null +++ b/client/src/app/account/account-details/account-details.component.ts | |||
@@ -0,0 +1,68 @@ | |||
1 | import { Component, OnInit, Input } from '@angular/core'; | ||
2 | import { FormBuilder, FormGroup } from '@angular/forms'; | ||
3 | import { Router } from '@angular/router'; | ||
4 | |||
5 | import { NotificationsService } from 'angular2-notifications'; | ||
6 | |||
7 | import { AuthService } from '../../core'; | ||
8 | import { | ||
9 | FormReactive, | ||
10 | User, | ||
11 | UserService, | ||
12 | USER_PASSWORD | ||
13 | } from '../../shared'; | ||
14 | |||
15 | @Component({ | ||
16 | selector: 'my-account-details', | ||
17 | templateUrl: './account-details.component.html' | ||
18 | }) | ||
19 | |||
20 | export class AccountDetailsComponent extends FormReactive implements OnInit { | ||
21 | @Input() user: User = null; | ||
22 | |||
23 | error: string = null; | ||
24 | |||
25 | form: FormGroup; | ||
26 | formErrors = {}; | ||
27 | validationMessages = {}; | ||
28 | |||
29 | constructor( | ||
30 | private authService: AuthService, | ||
31 | private formBuilder: FormBuilder, | ||
32 | private router: Router, | ||
33 | private notificationsService: NotificationsService, | ||
34 | private userService: UserService | ||
35 | ) { | ||
36 | super(); | ||
37 | } | ||
38 | |||
39 | buildForm() { | ||
40 | this.form = this.formBuilder.group({ | ||
41 | displayNSFW: [ this.user.displayNSFW ], | ||
42 | }); | ||
43 | |||
44 | this.form.valueChanges.subscribe(data => this.onValueChanged(data)); | ||
45 | } | ||
46 | |||
47 | ngOnInit() { | ||
48 | this.buildForm(); | ||
49 | } | ||
50 | |||
51 | updateDetails() { | ||
52 | const displayNSFW = this.form.value['displayNSFW']; | ||
53 | const details = { | ||
54 | displayNSFW | ||
55 | }; | ||
56 | |||
57 | this.error = null; | ||
58 | this.userService.updateDetails(details).subscribe( | ||
59 | () => { | ||
60 | this.notificationsService.success('Success', 'Informations updated.'); | ||
61 | |||
62 | this.authService.refreshUserInformations(); | ||
63 | }, | ||
64 | |||
65 | err => this.error = err | ||
66 | ); | ||
67 | } | ||
68 | } | ||
diff --git a/client/src/app/account/account-details/index.ts b/client/src/app/account/account-details/index.ts new file mode 100644 index 000000000..28f644738 --- /dev/null +++ b/client/src/app/account/account-details/index.ts | |||
@@ -0,0 +1 @@ | |||
export * from './account-details.component'; | |||
diff --git a/client/src/app/account/account.component.html b/client/src/app/account/account.component.html index 2fbb5a908..6f10e79cd 100644 --- a/client/src/app/account/account.component.html +++ b/client/src/app/account/account.component.html | |||
@@ -1,26 +1,11 @@ | |||
1 | <h3>Account</h3> | 1 | <h3>Account</h3> |
2 | 2 | ||
3 | <div *ngIf="error" class="alert alert-danger">{{ error }}</div> | 3 | <div class="block"> |
4 | 4 | <h4>Change password</h4> | |
5 | <form role="form" (ngSubmit)="changePassword()" [formGroup]="form"> | 5 | <my-account-change-password></my-account-change-password> |
6 | <div class="form-group"> | 6 | </div> |
7 | <label for="new-password">New password</label> | 7 | |
8 | <input | 8 | <div class="block"> |
9 | type="password" class="form-control" id="new-password" | 9 | <h4>Update my informations</h4> |
10 | formControlName="new-password" | 10 | <my-account-details [user]="user"></my-account-details> |
11 | > | 11 | </div> |
12 | <div *ngIf="formErrors['new-password']" class="alert alert-danger"> | ||
13 | {{ formErrors['new-password'] }} | ||
14 | </div> | ||
15 | </div> | ||
16 | |||
17 | <div class="form-group"> | ||
18 | <label for="name">Confirm new password</label> | ||
19 | <input | ||
20 | type="password" class="form-control" id="new-confirmed-password" | ||
21 | formControlName="new-confirmed-password" | ||
22 | > | ||
23 | </div> | ||
24 | |||
25 | <input type="submit" value="Change password" class="btn btn-default" [disabled]="!form.valid"> | ||
26 | </form> | ||
diff --git a/client/src/app/account/account.component.scss b/client/src/app/account/account.component.scss new file mode 100644 index 000000000..e0437e792 --- /dev/null +++ b/client/src/app/account/account.component.scss | |||
@@ -0,0 +1,3 @@ | |||
1 | .block { | ||
2 | margin-top: 40px; | ||
3 | } | ||
diff --git a/client/src/app/account/account.component.ts b/client/src/app/account/account.component.ts index 14452a73e..57b3d4ccd 100644 --- a/client/src/app/account/account.component.ts +++ b/client/src/app/account/account.component.ts | |||
@@ -4,63 +4,25 @@ import { Router } from '@angular/router'; | |||
4 | 4 | ||
5 | import { NotificationsService } from 'angular2-notifications'; | 5 | import { NotificationsService } from 'angular2-notifications'; |
6 | 6 | ||
7 | import { FormReactive, UserService, USER_PASSWORD } from '../shared'; | 7 | import { AuthService } from '../core'; |
8 | import { | ||
9 | FormReactive, | ||
10 | User, | ||
11 | UserService, | ||
12 | USER_PASSWORD | ||
13 | } from '../shared'; | ||
8 | 14 | ||
9 | @Component({ | 15 | @Component({ |
10 | selector: 'my-account', | 16 | selector: 'my-account', |
11 | templateUrl: './account.component.html' | 17 | templateUrl: './account.component.html', |
18 | styleUrls: [ './account.component.scss' ] | ||
12 | }) | 19 | }) |
20 | export class AccountComponent implements OnInit { | ||
21 | user: User = null; | ||
13 | 22 | ||
14 | export class AccountComponent extends FormReactive implements OnInit { | 23 | constructor(private authService: AuthService) {} |
15 | error: string = null; | ||
16 | |||
17 | form: FormGroup; | ||
18 | formErrors = { | ||
19 | 'new-password': '', | ||
20 | 'new-confirmed-password': '' | ||
21 | }; | ||
22 | validationMessages = { | ||
23 | 'new-password': USER_PASSWORD.MESSAGES, | ||
24 | 'new-confirmed-password': USER_PASSWORD.MESSAGES | ||
25 | }; | ||
26 | |||
27 | constructor( | ||
28 | private formBuilder: FormBuilder, | ||
29 | private router: Router, | ||
30 | private notificationsService: NotificationsService, | ||
31 | private userService: UserService | ||
32 | ) { | ||
33 | super(); | ||
34 | } | ||
35 | |||
36 | buildForm() { | ||
37 | this.form = this.formBuilder.group({ | ||
38 | 'new-password': [ '', USER_PASSWORD.VALIDATORS ], | ||
39 | 'new-confirmed-password': [ '', USER_PASSWORD.VALIDATORS ], | ||
40 | }); | ||
41 | |||
42 | this.form.valueChanges.subscribe(data => this.onValueChanged(data)); | ||
43 | } | ||
44 | 24 | ||
45 | ngOnInit() { | 25 | ngOnInit() { |
46 | this.buildForm(); | 26 | this.user = this.authService.getUser(); |
47 | } | ||
48 | |||
49 | changePassword() { | ||
50 | const newPassword = this.form.value['new-password']; | ||
51 | const newConfirmedPassword = this.form.value['new-confirmed-password']; | ||
52 | |||
53 | this.error = null; | ||
54 | |||
55 | if (newPassword !== newConfirmedPassword) { | ||
56 | this.error = 'The new password and the confirmed password do not correspond.'; | ||
57 | return; | ||
58 | } | ||
59 | |||
60 | this.userService.changePassword(newPassword).subscribe( | ||
61 | () => this.notificationsService.success('Success', 'Password updated.'), | ||
62 | |||
63 | err => this.error = err | ||
64 | ); | ||
65 | } | 27 | } |
66 | } | 28 | } |
diff --git a/client/src/app/account/account.module.ts b/client/src/app/account/account.module.ts index 75f2ee6f9..f6c141ae6 100644 --- a/client/src/app/account/account.module.ts +++ b/client/src/app/account/account.module.ts | |||
@@ -2,6 +2,8 @@ import { NgModule } from '@angular/core'; | |||
2 | 2 | ||
3 | import { AccountRoutingModule } from './account-routing.module'; | 3 | import { AccountRoutingModule } from './account-routing.module'; |
4 | import { AccountComponent } from './account.component'; | 4 | import { AccountComponent } from './account.component'; |
5 | import { AccountChangePasswordComponent } from './account-change-password'; | ||
6 | import { AccountDetailsComponent } from './account-details'; | ||
5 | import { AccountService } from './account.service'; | 7 | import { AccountService } from './account.service'; |
6 | import { SharedModule } from '../shared'; | 8 | import { SharedModule } from '../shared'; |
7 | 9 | ||
@@ -12,7 +14,9 @@ import { SharedModule } from '../shared'; | |||
12 | ], | 14 | ], |
13 | 15 | ||
14 | declarations: [ | 16 | declarations: [ |
15 | AccountComponent | 17 | AccountComponent, |
18 | AccountChangePasswordComponent, | ||
19 | AccountDetailsComponent | ||
16 | ], | 20 | ], |
17 | 21 | ||
18 | exports: [ | 22 | exports: [ |
diff --git a/client/src/app/core/auth/auth-user.model.ts b/client/src/app/core/auth/auth-user.model.ts index cb7e88d19..7115b9781 100644 --- a/client/src/app/core/auth/auth-user.model.ts +++ b/client/src/app/core/auth/auth-user.model.ts | |||
@@ -67,7 +67,7 @@ export class AuthUser extends User { | |||
67 | localStorage.setItem(AuthUser.KEYS.ID, this.id.toString()); | 67 | localStorage.setItem(AuthUser.KEYS.ID, this.id.toString()); |
68 | localStorage.setItem(AuthUser.KEYS.USERNAME, this.username); | 68 | localStorage.setItem(AuthUser.KEYS.USERNAME, this.username); |
69 | localStorage.setItem(AuthUser.KEYS.ROLE, this.role); | 69 | localStorage.setItem(AuthUser.KEYS.ROLE, this.role); |
70 | localStorage.setItem(AuthUser.KEYS.DISPLAY_NSFW, JSON.stringify(this.displayNSFW); | 70 | localStorage.setItem(AuthUser.KEYS.DISPLAY_NSFW, JSON.stringify(this.displayNSFW)); |
71 | this.tokens.save(); | 71 | this.tokens.save(); |
72 | } | 72 | } |
73 | } | 73 | } |
diff --git a/client/src/app/core/auth/auth.service.ts b/client/src/app/core/auth/auth.service.ts index 2e7328197..00a4216ef 100644 --- a/client/src/app/core/auth/auth.service.ts +++ b/client/src/app/core/auth/auth.service.ts | |||
@@ -125,7 +125,7 @@ export class AuthService { | |||
125 | res.username = username; | 125 | res.username = username; |
126 | return res; | 126 | return res; |
127 | }) | 127 | }) |
128 | .flatMap(res => this.fetchUserInformations(res)) | 128 | .flatMap(res => this.mergeUserInformations(res)) |
129 | .map(res => this.handleLogin(res)) | 129 | .map(res => this.handleLogin(res)) |
130 | .catch((res) => this.restExtractor.handleError(res)); | 130 | .catch((res) => this.restExtractor.handleError(res)); |
131 | } | 131 | } |
@@ -178,7 +178,23 @@ export class AuthService { | |||
178 | }); | 178 | }); |
179 | } | 179 | } |
180 | 180 | ||
181 | private fetchUserInformations (obj: any) { | 181 | refreshUserInformations() { |
182 | const obj = { | ||
183 | access_token: this.user.getAccessToken() | ||
184 | }; | ||
185 | |||
186 | this.mergeUserInformations(obj) | ||
187 | .subscribe( | ||
188 | res => { | ||
189 | this.user.displayNSFW = res.displayNSFW; | ||
190 | this.user.role = res.role; | ||
191 | |||
192 | this.user.save(); | ||
193 | } | ||
194 | ); | ||
195 | } | ||
196 | |||
197 | private mergeUserInformations(obj: { access_token: string }) { | ||
182 | // Do not call authHttp here to avoid circular dependencies headaches | 198 | // Do not call authHttp here to avoid circular dependencies headaches |
183 | 199 | ||
184 | const headers = new Headers(); | 200 | const headers = new Headers(); |
@@ -187,9 +203,13 @@ export class AuthService { | |||
187 | return this.http.get(AuthService.BASE_USER_INFORMATIONS_URL, { headers }) | 203 | return this.http.get(AuthService.BASE_USER_INFORMATIONS_URL, { headers }) |
188 | .map(res => res.json()) | 204 | .map(res => res.json()) |
189 | .map(res => { | 205 | .map(res => { |
190 | obj.id = res.id; | 206 | const newProperties = { |
191 | obj.role = res.role; | 207 | id: res.id, |
192 | return obj; | 208 | role: res.role, |
209 | displayNSFW: res.displayNSFW | ||
210 | }; | ||
211 | |||
212 | return Object.assign(obj, newProperties); | ||
193 | } | 213 | } |
194 | ); | 214 | ); |
195 | } | 215 | } |
@@ -198,13 +218,14 @@ export class AuthService { | |||
198 | const id = obj.id; | 218 | const id = obj.id; |
199 | const username = obj.username; | 219 | const username = obj.username; |
200 | const role = obj.role; | 220 | const role = obj.role; |
221 | const displayNSFW = obj.displayNSFW; | ||
201 | const hashTokens = { | 222 | const hashTokens = { |
202 | access_token: obj.access_token, | 223 | access_token: obj.access_token, |
203 | token_type: obj.token_type, | 224 | token_type: obj.token_type, |
204 | refresh_token: obj.refresh_token | 225 | refresh_token: obj.refresh_token |
205 | }; | 226 | }; |
206 | 227 | ||
207 | this.user = new AuthUser({ id, username, role }, hashTokens); | 228 | this.user = new AuthUser({ id, username, role, displayNSFW }, hashTokens); |
208 | this.user.save(); | 229 | this.user.save(); |
209 | 230 | ||
210 | this.setStatus(AuthStatus.LoggedIn); | 231 | this.setStatus(AuthStatus.LoggedIn); |
diff --git a/client/src/app/shared/users/user.service.ts b/client/src/app/shared/users/user.service.ts index 865e04d48..0d41b900d 100644 --- a/client/src/app/shared/users/user.service.ts +++ b/client/src/app/shared/users/user.service.ts | |||
@@ -33,4 +33,12 @@ export class UserService { | |||
33 | .map(this.restExtractor.extractDataBool) | 33 | .map(this.restExtractor.extractDataBool) |
34 | .catch((res) => this.restExtractor.handleError(res)); | 34 | .catch((res) => this.restExtractor.handleError(res)); |
35 | } | 35 | } |
36 | |||
37 | updateDetails(details: { displayNSFW: boolean }) { | ||
38 | const url = UserService.BASE_USERS_URL + this.authService.getUser().id; | ||
39 | |||
40 | return this.authHttp.put(url, details) | ||
41 | .map(this.restExtractor.extractDataBool) | ||
42 | .catch((res) => this.restExtractor.handleError(res)); | ||
43 | } | ||
36 | } | 44 | } |