Merge pull request #14 from HideyoshiNakazone/implements-other-popups
Implements Other Popups
This commit is contained in:
4069
package-lock.json
generated
4069
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -28,7 +28,7 @@
|
|||||||
"@fortawesome/free-brands-svg-icons": "^6.1.1",
|
"@fortawesome/free-brands-svg-icons": "^6.1.1",
|
||||||
"@fortawesome/free-regular-svg-icons": "^6.1.1",
|
"@fortawesome/free-regular-svg-icons": "^6.1.1",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.1.1",
|
"@fortawesome/free-solid-svg-icons": "^6.1.1",
|
||||||
"bootstrap": "^4.6.1",
|
"bootstrap": "^4.6.2",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"express": "^4.18.1",
|
"express": "^4.18.1",
|
||||||
"jquery": "^3.6.0",
|
"jquery": "^3.6.0",
|
||||||
@@ -41,12 +41,12 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-builders/custom-webpack": "^14.1.0",
|
"@angular-builders/custom-webpack": "^14.1.0",
|
||||||
"@angular-devkit/build-angular": "^14.0.3",
|
"@angular-devkit/build-angular": "^14.0.3",
|
||||||
"@angular-eslint/builder": "14.0.0",
|
"@angular-eslint/builder": "^14.4.0",
|
||||||
"@angular-eslint/eslint-plugin": "14.0.0",
|
"@angular-eslint/eslint-plugin": "14.0.0",
|
||||||
"@angular-eslint/eslint-plugin-template": "14.0.0",
|
"@angular-eslint/eslint-plugin-template": "14.0.0",
|
||||||
"@angular-eslint/schematics": "14.0.0",
|
"@angular-eslint/schematics": "14.0.0",
|
||||||
"@angular-eslint/template-parser": "14.0.0",
|
"@angular-eslint/template-parser": "14.0.0",
|
||||||
"@angular/cli": "~14.0.3",
|
"@angular/cli": "^14.2.12",
|
||||||
"@angular/compiler-cli": "^14.0.0",
|
"@angular/compiler-cli": "^14.0.0",
|
||||||
"@types/jasmine": "~4.0.0",
|
"@types/jasmine": "~4.0.0",
|
||||||
"@types/node": "^18.11.19",
|
"@types/node": "^18.11.19",
|
||||||
|
|||||||
@@ -26,13 +26,13 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="user-management" *ngIf="this.user">
|
<ul class="user-management" *ngIf="this.user">
|
||||||
<li class="dropdown-item">
|
<li class="dropdown-item" (click)="onMyProfileClicked()">
|
||||||
<div class="icon-box">
|
<div class="icon-box">
|
||||||
<fa-icon class="fas fa-user" [icon]="userIcon"></fa-icon>
|
<fa-icon class="fas fa-user" [icon]="userIcon"></fa-icon>
|
||||||
</div>
|
</div>
|
||||||
<p>My Profile</p>
|
<p>My Profile</p>
|
||||||
</li>
|
</li>
|
||||||
<li class="dropdown-item">
|
<li class="dropdown-item" (click)="onHelpClicked()">
|
||||||
<div class="icon-box">
|
<div class="icon-box">
|
||||||
<fa-icon class="fas fa-question-circle" [icon]="questionCircleIcon"></fa-icon>
|
<fa-icon class="fas fa-question-circle" [icon]="questionCircleIcon"></fa-icon>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||||
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
|
import {Component, ComponentRef, EventEmitter, Input, OnDestroy, OnInit, Output, ViewContainerRef} from '@angular/core';
|
||||||
import { faEdit, faQuestionCircle, faSignOutAlt, faUser } from '@fortawesome/free-solid-svg-icons';
|
import { faEdit, faQuestionCircle, faSignOutAlt, faUser } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { Subscription, timeout } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { AuthService } from 'src/app/shared/auth/auth.service';
|
import { AuthService } from 'src/app/shared/auth/auth.service';
|
||||||
import UserChecker from 'src/app/shared/model/user/user.checker';
|
import {User} from "../../shared/model/user/user.model";
|
||||||
import { User } from 'src/app/shared/model/user/user.model';
|
import UserChecker from "../../shared/model/user/user.checker";
|
||||||
|
import {HelpComponent} from "../header-popup/help/help.component";
|
||||||
|
import {MyProfileComponent} from "../header-popup/my-profile/my-profile.component";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-header-dropdown',
|
selector: 'app-header-dropdown',
|
||||||
@@ -52,7 +54,13 @@ export class HeaderDropdownComponent implements OnInit, OnDestroy {
|
|||||||
@Output()
|
@Output()
|
||||||
signupPopupState: EventEmitter<boolean> = new EventEmitter();
|
signupPopupState: EventEmitter<boolean> = new EventEmitter();
|
||||||
|
|
||||||
constructor(private authService: AuthService) { }
|
@Output()
|
||||||
|
helpPopupState: EventEmitter<boolean> = new EventEmitter();
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
myProfilePopupState: EventEmitter<boolean> = new EventEmitter();
|
||||||
|
|
||||||
|
constructor(private viewContainerRef: ViewContainerRef, private authService: AuthService) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.userSubscription = this.authService.authSubject.subscribe(
|
this.userSubscription = this.authService.authSubject.subscribe(
|
||||||
@@ -86,8 +94,15 @@ export class HeaderDropdownComponent implements OnInit, OnDestroy {
|
|||||||
this.signupPopupState.emit(true);
|
this.signupPopupState.emit(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMyProfileClicked() {
|
||||||
|
this.myProfilePopupState.emit(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
onHelpClicked() {
|
||||||
|
this.helpPopupState.emit(true);
|
||||||
|
}
|
||||||
|
|
||||||
onLogout() {
|
onLogout() {
|
||||||
this.authService.logout();
|
this.authService.logout();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
7
src/app/header/header-popup/help/help.component.css
Normal file
7
src/app/header/header-popup/help/help.component.css
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
.help-container {
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: #555555;
|
||||||
|
}
|
||||||
28
src/app/header/header-popup/help/help.component.html
Normal file
28
src/app/header/header-popup/help/help.component.html
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<app-popup [state]="state"
|
||||||
|
(stateChange)="onStateChange($event)"
|
||||||
|
[ignoreClickOutside]="ignoreClickOutside">
|
||||||
|
|
||||||
|
<div class="help-container container m-0 overflow-hidden">
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This is a simple example project to demonstrate
|
||||||
|
User Authentication and Authorization using
|
||||||
|
<a href="https://spring.io/projects/spring-security" target="_blank">Spring Security</a>
|
||||||
|
and
|
||||||
|
<a href="https://docs.spring.io/spring-security/reference/servlet/oauth2/" target="_blank">OAuth2</a>.
|
||||||
|
|
||||||
|
<br/><br/>
|
||||||
|
|
||||||
|
The only data stored is your email address, username and name.
|
||||||
|
This data is stored in a database and is used to authenticate you
|
||||||
|
and will not be used for any other purpose.
|
||||||
|
|
||||||
|
<br/><br/>
|
||||||
|
|
||||||
|
All data can be deleted by clicking the "Delete Account" button
|
||||||
|
on the "My Profile" option.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</app-popup>
|
||||||
23
src/app/header/header-popup/help/help.component.spec.ts
Normal file
23
src/app/header/header-popup/help/help.component.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { HelpComponent } from './help.component';
|
||||||
|
|
||||||
|
describe('HelpComponent', () => {
|
||||||
|
let component: HelpComponent;
|
||||||
|
let fixture: ComponentFixture<HelpComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ HelpComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(HelpComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
24
src/app/header/header-popup/help/help.component.ts
Normal file
24
src/app/header/header-popup/help/help.component.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import {Component, EventEmitter, Input, Output} from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-help',
|
||||||
|
templateUrl: './help.component.html',
|
||||||
|
styleUrls: ['./help.component.css']
|
||||||
|
})
|
||||||
|
export class HelpComponent {
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
state: boolean = false;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
ignoreClickOutside!: HTMLDivElement[];
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
stateChange = new EventEmitter<boolean>();
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
onStateChange(state: boolean) {
|
||||||
|
this.stateChange.emit(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
100
src/app/header/header-popup/my-profile/my-profile.component.css
Normal file
100
src/app/header/header-popup/my-profile/my-profile.component.css
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-options-container {
|
||||||
|
justify-content: space-around;
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column;
|
||||||
|
align-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 200px;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-options-row {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.separator-line {
|
||||||
|
justify-content: center;
|
||||||
|
align-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin: 30px 0;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
width: 100%;
|
||||||
|
border-bottom: 2px solid #80808076;
|
||||||
|
border-radius: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-options-container button {
|
||||||
|
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 16px;
|
||||||
|
border: none;
|
||||||
|
height: 50px;
|
||||||
|
width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picture-btn {
|
||||||
|
background-color: rgba(118, 118, 118, 0.7) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-btn {
|
||||||
|
background-color: rgba(216, 41, 28, 0.7) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@media (min-width:767px) {
|
||||||
|
|
||||||
|
.profile-options-container {
|
||||||
|
all: unset;
|
||||||
|
justify-content: space-around;
|
||||||
|
width: 600px;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-options-container .row {
|
||||||
|
all: unset;
|
||||||
|
display: flex;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-container {
|
||||||
|
justify-content: center;
|
||||||
|
align-content: center;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.separator-line {
|
||||||
|
all: unset;
|
||||||
|
justify-content: center;
|
||||||
|
align-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0px 60px;
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
all: unset;
|
||||||
|
border-right: 2px solid #80808076;
|
||||||
|
border-radius: 50px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<app-popup [state]="state"
|
||||||
|
(stateChange)="onStateChange($event)"
|
||||||
|
[ignoreClickOutside]="ignoreClickOutside">
|
||||||
|
|
||||||
|
<div class="container m-0 overflow-hidden"
|
||||||
|
[@resizeContainerForErrorMessage]="hideErrorMessage()">
|
||||||
|
|
||||||
|
<app-error-box [errorMessage]="errorMessage"
|
||||||
|
[@showErrorMessage]="showErrorMessage()">
|
||||||
|
</app-error-box>
|
||||||
|
|
||||||
|
<div class="container profile-options-container"
|
||||||
|
[@hideAuthContainer]="hideErrorMessage()"
|
||||||
|
(@hideAuthContainer.done)="hideAuthContainer($event)">
|
||||||
|
|
||||||
|
<div class="row profile-options-row">
|
||||||
|
|
||||||
|
<div class="btn-container">
|
||||||
|
<app-profile-picture-picker
|
||||||
|
(imageSent)="onProfilePictureSent($event)">
|
||||||
|
</app-profile-picture-picker>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="separator-line">
|
||||||
|
<div class="line"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="btn-container">
|
||||||
|
<button class="delete-btn"
|
||||||
|
(click)="onDeleteAccount()">
|
||||||
|
Delete Account
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</app-popup>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { MyProfileComponent } from './my-profile.component';
|
||||||
|
|
||||||
|
describe('MyProfileComponent', () => {
|
||||||
|
let component: MyProfileComponent;
|
||||||
|
let fixture: ComponentFixture<MyProfileComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ MyProfileComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(MyProfileComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
162
src/app/header/header-popup/my-profile/my-profile.component.ts
Normal file
162
src/app/header/header-popup/my-profile/my-profile.component.ts
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
import {ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
|
||||||
|
import {AuthService} from "../../../shared/auth/auth.service";
|
||||||
|
import {User} from "../../../shared/model/user/user.model";
|
||||||
|
import {animate, animateChild, group, query, state, style, transition, trigger} from "@angular/animations";
|
||||||
|
import {MatIconRegistry} from "@angular/material/icon";
|
||||||
|
import {DomSanitizer} from "@angular/platform-browser";
|
||||||
|
import {FormControl, FormGroup, Validators} from "@angular/forms";
|
||||||
|
import {ValidateNotEmptyValidator} from "../../../shared/validators/validate-not-empty.validator";
|
||||||
|
import {ValidatePasswordValidator} from "../../../shared/validators/validate-password.validator";
|
||||||
|
import {first, take} from "rxjs";
|
||||||
|
import UserChecker from "../../../shared/model/user/user.checker";
|
||||||
|
import HttpErrorChecker from "../../../shared/model/httpError/httpErrorChecker";
|
||||||
|
import {HttpError} from "../../../shared/model/httpError/httpError.model";
|
||||||
|
import {faFileUpload} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-my-profile',
|
||||||
|
templateUrl: './my-profile.component.html',
|
||||||
|
styleUrls: ['./my-profile.component.css'],
|
||||||
|
animations: [
|
||||||
|
trigger('resizeContainerForErrorMessage', [
|
||||||
|
state('hide',
|
||||||
|
style({
|
||||||
|
height: '100px',
|
||||||
|
width: '320px',
|
||||||
|
})
|
||||||
|
),
|
||||||
|
transition(
|
||||||
|
'show => hide',
|
||||||
|
group([
|
||||||
|
query(
|
||||||
|
"@*",
|
||||||
|
animateChild(),
|
||||||
|
{ optional: true }
|
||||||
|
),
|
||||||
|
animate('1s ease')
|
||||||
|
])
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
trigger('showErrorMessage', [
|
||||||
|
state('show',
|
||||||
|
style({
|
||||||
|
opacity: 1,
|
||||||
|
height: '100px',
|
||||||
|
width: '320px',
|
||||||
|
})
|
||||||
|
),
|
||||||
|
state('hide',
|
||||||
|
style({
|
||||||
|
opacity: 0,
|
||||||
|
height: '0px',
|
||||||
|
width: '0px',
|
||||||
|
})
|
||||||
|
),
|
||||||
|
transition(
|
||||||
|
'* => show',
|
||||||
|
animate(
|
||||||
|
'500ms ease-in'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
trigger('hideAuthContainer', [
|
||||||
|
state('hide',
|
||||||
|
style({
|
||||||
|
opacity: 0,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
transition(
|
||||||
|
'show => hide',
|
||||||
|
group([
|
||||||
|
query(
|
||||||
|
"@*",
|
||||||
|
animateChild(),
|
||||||
|
{ optional: true }
|
||||||
|
),
|
||||||
|
animate(
|
||||||
|
'250ms ease-out'
|
||||||
|
)
|
||||||
|
])
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class MyProfileComponent implements OnInit {
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
state: boolean = false;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
user!: User | null;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
ignoreClickOutside!: HTMLDivElement[];
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
stateChange = new EventEmitter<boolean>();
|
||||||
|
|
||||||
|
alterForm!: FormGroup;
|
||||||
|
|
||||||
|
errorMessage!: string | null;
|
||||||
|
|
||||||
|
isShowErrorMessage = false;
|
||||||
|
|
||||||
|
_fileIcon = faFileUpload
|
||||||
|
|
||||||
|
constructor(private authService: AuthService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.alterForm = new FormGroup({
|
||||||
|
'username': new FormControl(null, [Validators.required, ValidateNotEmptyValidator]),
|
||||||
|
'password': new FormControl(null, [Validators.required, ValidatePasswordValidator])
|
||||||
|
});
|
||||||
|
this.errorMessage = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
onStateChange(state: boolean) {
|
||||||
|
this.stateChange.emit(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
showErrorMessage(): string {
|
||||||
|
if (this.isShowErrorMessage) {
|
||||||
|
return "show";
|
||||||
|
}
|
||||||
|
return "hide";
|
||||||
|
}
|
||||||
|
|
||||||
|
hideErrorMessage(): string {
|
||||||
|
if (!!this.errorMessage) {
|
||||||
|
return "hide";
|
||||||
|
}
|
||||||
|
return "show";
|
||||||
|
}
|
||||||
|
|
||||||
|
onDeleteAccount() {
|
||||||
|
this.authService.deleteAccount().subscribe({
|
||||||
|
next: (response: any) => {
|
||||||
|
this.authService.logout();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.closePopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
hideAuthContainer(event: any) {
|
||||||
|
if (event.toState === "hide") {
|
||||||
|
event.element.style.display = "none";
|
||||||
|
this.isShowErrorMessage = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onProfilePictureSent(event: any) {
|
||||||
|
if (event) {
|
||||||
|
this.closePopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private closePopup() {
|
||||||
|
this.onStateChange(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<div class="btn-container">
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<div class="custom-file">
|
||||||
|
<input type="file"
|
||||||
|
class="custom-file-input"
|
||||||
|
id="inputProfilePicture"
|
||||||
|
aria-describedby="inputProfilePicture"
|
||||||
|
(change)="handleFileInput($event)">
|
||||||
|
<label class="custom-file-label"
|
||||||
|
for="inputProfilePicture">
|
||||||
|
{{isProfilePictureSelected ? getFileName() : 'Profile Picture'}}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button class="btn btn-outline-secondary"
|
||||||
|
[disabled]="!isProfilePictureSelected"
|
||||||
|
(click)="uploadProfilePicture()">Upload</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ProfilePicturePickerComponent } from './profile-picture-picker.component';
|
||||||
|
|
||||||
|
describe('ProfilePicturePickerComponent', () => {
|
||||||
|
let component: ProfilePicturePickerComponent;
|
||||||
|
let fixture: ComponentFixture<ProfilePicturePickerComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ ProfilePicturePickerComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ProfilePicturePickerComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import {Component, EventEmitter, Output} from '@angular/core';
|
||||||
|
import {AuthService} from "../../../../shared/auth/auth.service";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-profile-picture-picker',
|
||||||
|
templateUrl: './profile-picture-picker.component.html',
|
||||||
|
styleUrls: ['./profile-picture-picker.component.css']
|
||||||
|
})
|
||||||
|
export class ProfilePicturePickerComponent {
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
imageSent = new EventEmitter<boolean>();
|
||||||
|
|
||||||
|
private profilePicture!: File;
|
||||||
|
|
||||||
|
constructor(private authService: AuthService) { }
|
||||||
|
|
||||||
|
handleFileInput(event: Event) {
|
||||||
|
const element = event.currentTarget as HTMLInputElement;
|
||||||
|
const fileList: FileList | null = element.files;
|
||||||
|
if (fileList != null && fileList.length > 0 && fileList[0] != null) {
|
||||||
|
this.profilePicture = fileList[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadProfilePicture() {
|
||||||
|
this.authService.addProfilePicture(this.profilePicture);
|
||||||
|
this.imageSent.emit(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
getFileName(): string {
|
||||||
|
return this.profilePicture.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isProfilePictureSelected(): boolean {
|
||||||
|
return !!this.profilePicture;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -56,6 +56,11 @@
|
|||||||
width: 45px;
|
width: 45px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.profile-picture {
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
.profile .profile-btn {
|
.profile .profile-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
border: 5px solid #ffffff;
|
border: 5px solid #ffffff;
|
||||||
@@ -63,10 +68,10 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
height: 45px;
|
height: 50px;
|
||||||
width: 45px;
|
width: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile .profile-btn fa-icon {
|
.profile .profile-btn fa-icon {
|
||||||
font-size: 25px;
|
font-size: 28px;
|
||||||
}
|
}
|
||||||
@@ -16,7 +16,13 @@
|
|||||||
#profile>
|
#profile>
|
||||||
<div class="profile-btn"
|
<div class="profile-btn"
|
||||||
(click)="onProfileButtonClicked()">
|
(click)="onProfileButtonClicked()">
|
||||||
<fa-icon class="fas fa-user" [icon]="userIcon"></fa-icon>
|
<fa-icon *ngIf="!loggedUser || !(loggedUser.profilePictureUrl)"
|
||||||
|
class="fas fa-user" [icon]="userIcon"></fa-icon>
|
||||||
|
<img *ngIf="!!loggedUser && !!(loggedUser.profilePictureUrl)"
|
||||||
|
class="profile-picture"
|
||||||
|
[ngSrc]="loggedUser.profilePictureUrl"
|
||||||
|
width="50" height="50"
|
||||||
|
alt="Profile Picture" priority/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,13 +1,17 @@
|
|||||||
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
|
import {Component, EventEmitter, OnDestroy, OnInit, Output} from '@angular/core';
|
||||||
import { faUser } from '@fortawesome/free-solid-svg-icons';
|
import { faUser } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { SliderItemComponent } from 'src/app/shared/components/slider-item/slider-item.component';
|
import { SliderItemComponent } from 'src/app/shared/components/slider-item/slider-item.component';
|
||||||
|
import UserChecker from "../../../shared/model/user/user.checker";
|
||||||
|
import {User} from "../../../shared/model/user/user.model";
|
||||||
|
import {AuthService} from "../../../shared/auth/auth.service";
|
||||||
|
import {Subscription} from "rxjs";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-nav-slider',
|
selector: 'app-nav-slider',
|
||||||
templateUrl: './nav-slider.component.html',
|
templateUrl: './nav-slider.component.html',
|
||||||
styleUrls: ['./nav-slider.component.css']
|
styleUrls: ['./nav-slider.component.css']
|
||||||
})
|
})
|
||||||
export class NavSliderComponent extends SliderItemComponent {
|
export class NavSliderComponent extends SliderItemComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
userIcon = faUser;
|
userIcon = faUser;
|
||||||
|
|
||||||
@@ -18,13 +22,33 @@ export class NavSliderComponent extends SliderItemComponent {
|
|||||||
{ page: "About", link: "/home" }
|
{ page: "About", link: "/home" }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
loggedUser!: User | null;
|
||||||
|
|
||||||
|
private userSubscription!: Subscription;
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
profileButtonClicked = new EventEmitter();
|
profileButtonClicked = new EventEmitter();
|
||||||
|
|
||||||
constructor() {
|
constructor(private authService: AuthService) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.userSubscription = this.authService.authSubject.subscribe(
|
||||||
|
res => {
|
||||||
|
if (res && UserChecker.test(res)) {
|
||||||
|
this.loggedUser = <User>res;
|
||||||
|
} else {
|
||||||
|
this.loggedUser = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.userSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
onProfileButtonClicked() {
|
onProfileButtonClicked() {
|
||||||
this.profileButtonClicked.emit();
|
this.profileButtonClicked.emit();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,13 +16,13 @@ export class UserSliderComponent extends SliderItemComponent implements OnInit {
|
|||||||
{
|
{
|
||||||
name: "Login",
|
name: "Login",
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
this.loginOptionClicked();
|
this.onLoginOptionClicked();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Signup",
|
name: "Signup",
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
this.signupOptionClicked();
|
this.onSignUpOptionClick();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -30,11 +30,15 @@ export class UserSliderComponent extends SliderItemComponent implements OnInit {
|
|||||||
userOptions = [
|
userOptions = [
|
||||||
{
|
{
|
||||||
name: "My Profile",
|
name: "My Profile",
|
||||||
onClick: () => {}
|
onClick: () => {
|
||||||
|
this.onMyProfileClicked()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Help",
|
name: "Help",
|
||||||
onClick: () => {}
|
onClick: () => {
|
||||||
|
this.onHelpClicked();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Logout",
|
name: "Logout",
|
||||||
@@ -48,12 +52,17 @@ export class UserSliderComponent extends SliderItemComponent implements OnInit {
|
|||||||
|
|
||||||
authSubscription!: Subscription;
|
authSubscription!: Subscription;
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
loginPopupState: EventEmitter<boolean> = new EventEmitter();
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
loginPopupState = new EventEmitter<boolean>();
|
signupPopupState: EventEmitter<boolean> = new EventEmitter();
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
signupPopupState = new EventEmitter<boolean>();
|
helpPopupState: EventEmitter<boolean> = new EventEmitter();
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
myProfilePopupState: EventEmitter<boolean> = new EventEmitter();
|
||||||
|
|
||||||
constructor(private authService: AuthService) {
|
constructor(private authService: AuthService) {
|
||||||
super();
|
super();
|
||||||
@@ -72,14 +81,22 @@ export class UserSliderComponent extends SliderItemComponent implements OnInit {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
loginOptionClicked(): void {
|
onLoginOptionClicked(): void {
|
||||||
this.loginPopupState.emit(true);
|
this.loginPopupState.emit(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
signupOptionClicked(): void {
|
onSignUpOptionClick(): void {
|
||||||
this.signupPopupState.emit(true);
|
this.signupPopupState.emit(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMyProfileClicked(): void {
|
||||||
|
this.myProfilePopupState.emit(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
onHelpClicked(): void {
|
||||||
|
this.helpPopupState.emit(true);
|
||||||
|
}
|
||||||
|
|
||||||
onLogout() {
|
onLogout() {
|
||||||
this.authService.logout();
|
this.authService.logout();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,6 +136,11 @@ app-header-slider {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.profile-picture {
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
.profile .profile-btn {
|
.profile .profile-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
border: 5px solid #ffffff;
|
border: 5px solid #ffffff;
|
||||||
@@ -143,8 +148,8 @@ app-header-slider {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
height: 45px;
|
height: 50px;
|
||||||
width: 45px;
|
width: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile .dropdown {
|
.profile .dropdown {
|
||||||
@@ -153,7 +158,7 @@ app-header-slider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.profile .profile-btn fa-icon {
|
.profile .profile-btn fa-icon {
|
||||||
font-size: 25px;
|
font-size: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.burger-container {
|
.burger-container {
|
||||||
|
|||||||
@@ -16,15 +16,24 @@
|
|||||||
|
|
||||||
<div class="profile" #profileDropdown>
|
<div class="profile" #profileDropdown>
|
||||||
<div class="profile-btn" (click)="toogleProfileDropdown()" #profileBtn>
|
<div class="profile-btn" (click)="toogleProfileDropdown()" #profileBtn>
|
||||||
<fa-icon class="fas fa-user" [icon]="userIcon"></fa-icon>
|
<fa-icon *ngIf="!loggedUser || !(loggedUser.profilePictureUrl)"
|
||||||
|
class="fas fa-user" [icon]="userIcon"></fa-icon>
|
||||||
|
<img *ngIf="!!loggedUser && !!(loggedUser.profilePictureUrl)"
|
||||||
|
class="profile-picture"
|
||||||
|
[ngSrc]="loggedUser.profilePictureUrl"
|
||||||
|
width="50" height="50"
|
||||||
|
alt="Profile Picture" priority/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<app-header-dropdown
|
<app-header-dropdown
|
||||||
class="dropdown"
|
class="dropdown"
|
||||||
(clickOutside)="closeDropdown()"
|
(clickOutside)="closeDropdown()"
|
||||||
[ignoreClickOutside]="[profileBtn]"
|
[ignoreClickOutside]="[profileBtn]"
|
||||||
[state]="profileDropdownState"
|
[state]="profileDropdownState"
|
||||||
(loginPopupState)="loginPopupStateChange($event)"
|
(loginPopupState)="loginPopupStateChange($event)"
|
||||||
(signupPopupState)="signupPopupStateChange($event)">
|
(signupPopupState)="signupPopupStateChange($event)"
|
||||||
|
(myProfilePopupState)="myProfilePopupStateChange($event)"
|
||||||
|
(helpPopupState)="helpPopupStateChange($event)">
|
||||||
</app-header-dropdown>
|
</app-header-dropdown>
|
||||||
</div>
|
</div>
|
||||||
<div class="burger-container" (click)="toogleNavSlider()">
|
<div class="burger-container" (click)="toogleNavSlider()">
|
||||||
@@ -51,20 +60,11 @@
|
|||||||
<app-user-slider
|
<app-user-slider
|
||||||
[state]="userSliderStatus"
|
[state]="userSliderStatus"
|
||||||
(loginPopupState)="loginPopupStateChange($event)"
|
(loginPopupState)="loginPopupStateChange($event)"
|
||||||
(signupPopupState)="signupPopupStateChange($event)">
|
(signupPopupState)="signupPopupStateChange($event)"
|
||||||
|
(myProfilePopupState)="myProfilePopupStateChange($event)"
|
||||||
|
(helpPopupState)="helpPopupStateChange($event)">
|
||||||
</app-user-slider>
|
</app-user-slider>
|
||||||
</app-header-slider>
|
</app-header-slider>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-spacer"></div>
|
<div class="header-spacer"></div>
|
||||||
|
|
||||||
<!-- <app-login
|
|
||||||
*ngIf="loginPopupState"
|
|
||||||
[(state)]="loginPopupState"
|
|
||||||
[ignoreClickOutside]="[profileBtn, profileDropdown, user]">
|
|
||||||
</app-login>
|
|
||||||
|
|
||||||
<app-signup
|
|
||||||
[(state)]="signupPopupState"
|
|
||||||
[ignoreClickOutside]="[profileBtn, profileDropdown, user]">
|
|
||||||
</app-signup> -->
|
|
||||||
@@ -1,14 +1,20 @@
|
|||||||
import { Component, ComponentRef, ElementRef, OnInit, ViewChild, ViewContainerRef } from '@angular/core';
|
import {Component, ComponentRef, ElementRef, OnDestroy, OnInit, ViewChild, ViewContainerRef} from '@angular/core';
|
||||||
import { faUser } from '@fortawesome/free-solid-svg-icons';
|
import { faUser } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { LoginComponent } from './header-popup/login/login.component';
|
import { LoginComponent } from './header-popup/login/login.component';
|
||||||
import { SignupComponent } from './header-popup/signup/signup.component';
|
import { SignupComponent } from './header-popup/signup/signup.component';
|
||||||
|
import {AuthService} from "../shared/auth/auth.service";
|
||||||
|
import UserChecker from "../shared/model/user/user.checker";
|
||||||
|
import {User} from "../shared/model/user/user.model";
|
||||||
|
import {Subscription} from "rxjs";
|
||||||
|
import {HelpComponent} from "./header-popup/help/help.component";
|
||||||
|
import {MyProfileComponent} from "./header-popup/my-profile/my-profile.component";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-header',
|
selector: 'app-header',
|
||||||
templateUrl: './header.component.html',
|
templateUrl: './header.component.html',
|
||||||
styleUrls: ['./header.component.css']
|
styleUrls: ['./header.component.css']
|
||||||
})
|
})
|
||||||
export class HeaderComponent {
|
export class HeaderComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
userIcon = faUser;
|
userIcon = faUser;
|
||||||
|
|
||||||
@@ -28,11 +34,35 @@ export class HeaderComponent {
|
|||||||
@ViewChild('user')
|
@ViewChild('user')
|
||||||
userElementRef!: ElementRef;
|
userElementRef!: ElementRef;
|
||||||
|
|
||||||
|
loggedUser!: User | null;
|
||||||
|
|
||||||
|
private userSubscription!: Subscription;
|
||||||
|
|
||||||
private loginComponent!: ComponentRef<LoginComponent>;
|
private loginComponent!: ComponentRef<LoginComponent>;
|
||||||
|
|
||||||
private signupComponent!: ComponentRef<SignupComponent>;
|
private signupComponent!: ComponentRef<SignupComponent>;
|
||||||
|
|
||||||
constructor(private viewContainerRef: ViewContainerRef) { }
|
private myProfileComponent!: ComponentRef<MyProfileComponent>;
|
||||||
|
|
||||||
|
private helpComponent!: ComponentRef<HelpComponent>;
|
||||||
|
|
||||||
|
constructor(private viewContainerRef: ViewContainerRef, private authService: AuthService) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.userSubscription = this.authService.authSubject.subscribe(
|
||||||
|
res => {
|
||||||
|
if (res && UserChecker.test(res)) {
|
||||||
|
this.loggedUser = <User>res;
|
||||||
|
} else {
|
||||||
|
this.loggedUser = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.userSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public toogleProfileDropdown(): void {
|
public toogleProfileDropdown(): void {
|
||||||
@@ -58,23 +88,36 @@ export class HeaderComponent {
|
|||||||
public closeDropdown(): void {
|
public closeDropdown(): void {
|
||||||
this.profileDropdownState = false;
|
this.profileDropdownState = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public closeNavSlider(): void {
|
|
||||||
if (this.userSliderStatus) {
|
|
||||||
this.userSliderStatus = false;
|
|
||||||
} else {
|
|
||||||
this.navSliderStatus = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public loginPopupStateChange(state: boolean): void {
|
public loginPopupStateChange(state: boolean): void {
|
||||||
|
|
||||||
if (state) {
|
if (state) {
|
||||||
this.createLoginPopup();
|
this.createLoginPopup();
|
||||||
} else {
|
} else {
|
||||||
this.closeLoginPopup();
|
this.closeLoginPopup();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public signupPopupStateChange(state: boolean): void {
|
||||||
|
if (state) {
|
||||||
|
this.createSignupPopup();
|
||||||
|
} else {
|
||||||
|
this.closeSignupPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
myProfilePopupStateChange(state: boolean): void {
|
||||||
|
if (state) {
|
||||||
|
this.createMyProfilePopup();
|
||||||
|
} else {
|
||||||
|
this.closeMyProfilePopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
helpPopupStateChange(state: boolean): void {
|
||||||
|
if (state) {
|
||||||
|
this.createHelpPopup();
|
||||||
|
} else {
|
||||||
|
this.closeHelpPopup();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private createLoginPopup(): void {
|
private createLoginPopup(): void {
|
||||||
@@ -123,6 +166,41 @@ export class HeaderComponent {
|
|||||||
this.profileDropdownState = false;
|
this.profileDropdownState = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createMyProfilePopup() {
|
||||||
|
this.myProfileComponent = this.viewContainerRef.createComponent(MyProfileComponent);
|
||||||
|
this.myProfileComponent.instance.state = true;
|
||||||
|
this.myProfileComponent.instance.user = this.loggedUser;
|
||||||
|
|
||||||
|
this.myProfileComponent.instance.stateChange.subscribe(
|
||||||
|
state => {
|
||||||
|
if (!state) {
|
||||||
|
this.closeMyProfilePopup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.navSliderStatus = false;
|
||||||
|
this.userSliderStatus = false;
|
||||||
|
this.profileDropdownState = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createHelpPopup() {
|
||||||
|
this.helpComponent = this.viewContainerRef.createComponent(HelpComponent);
|
||||||
|
this.helpComponent.instance.state = true;
|
||||||
|
|
||||||
|
this.helpComponent.instance.stateChange.subscribe(
|
||||||
|
state => {
|
||||||
|
if (!state) {
|
||||||
|
this.closeHelpPopup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.navSliderStatus = false;
|
||||||
|
this.userSliderStatus = false;
|
||||||
|
this.profileDropdownState = false;
|
||||||
|
}
|
||||||
|
|
||||||
private closeLoginPopup() {
|
private closeLoginPopup() {
|
||||||
this.loginComponent.destroy();
|
this.loginComponent.destroy();
|
||||||
}
|
}
|
||||||
@@ -131,15 +209,11 @@ export class HeaderComponent {
|
|||||||
this.signupComponent.destroy();
|
this.signupComponent.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
public signupPopupStateChange(state: boolean): void {
|
private closeMyProfilePopup() {
|
||||||
this.signupPopupState = state;
|
this.myProfileComponent.destroy();
|
||||||
|
|
||||||
if (state) {
|
|
||||||
this.createSignupPopup();
|
|
||||||
} else {
|
|
||||||
this.closeSignupPopup();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private closeHelpPopup() {
|
||||||
|
this.helpComponent.destroy();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -16,6 +16,9 @@ import { SignupComponent } from './header-popup/signup/signup.component';
|
|||||||
import { CallbackComponent } from './header-popup/callback/callback.component';
|
import { CallbackComponent } from './header-popup/callback/callback.component';
|
||||||
import {MatIconModule} from '@angular/material/icon';
|
import {MatIconModule} from '@angular/material/icon';
|
||||||
import { ErrorBoxComponent } from './header-popup/error-box/error-box.component';
|
import { ErrorBoxComponent } from './header-popup/error-box/error-box.component';
|
||||||
|
import { HelpComponent } from './header-popup/help/help.component';
|
||||||
|
import { MyProfileComponent } from './header-popup/my-profile/my-profile.component';
|
||||||
|
import { ProfilePicturePickerComponent } from './header-popup/my-profile/profile-picture-picker/profile-picture-picker.component';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -30,6 +33,9 @@ import { ErrorBoxComponent } from './header-popup/error-box/error-box.component'
|
|||||||
SignupComponent,
|
SignupComponent,
|
||||||
CallbackComponent,
|
CallbackComponent,
|
||||||
ErrorBoxComponent,
|
ErrorBoxComponent,
|
||||||
|
HelpComponent,
|
||||||
|
MyProfileComponent,
|
||||||
|
ProfilePicturePickerComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
|
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import {first, firstValueFrom, Observable, of, Subject, take} from 'rxjs';
|
import {first, firstValueFrom, map, Observable, of, Subject, take, tap} from 'rxjs';
|
||||||
import { catchError } from 'rxjs/operators';
|
import { catchError } from 'rxjs/operators';
|
||||||
import { environment } from 'src/environments/environment';
|
import { environment } from 'src/environments/environment';
|
||||||
import { HttpError } from '../model/httpError/httpError.model';
|
import { HttpError } from '../model/httpError/httpError.model';
|
||||||
import { Token } from '../model/token/token.model';
|
|
||||||
import { User } from '../model/user/user.model';
|
import { User } from '../model/user/user.model';
|
||||||
|
import * as http from "http";
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@@ -21,7 +21,6 @@ export class AuthService {
|
|||||||
readonly BACKEND_OAUTH_PATH = environment.backendOAuthPath;
|
readonly BACKEND_OAUTH_PATH = environment.backendOAuthPath;
|
||||||
|
|
||||||
constructor(private http: HttpClient) { }
|
constructor(private http: HttpClient) { }
|
||||||
|
|
||||||
login(userAuthAtempt: User): void {
|
login(userAuthAtempt: User): void {
|
||||||
this.validateUser(this.loginUser(userAuthAtempt));
|
this.validateUser(this.loginUser(userAuthAtempt));
|
||||||
}
|
}
|
||||||
@@ -56,14 +55,27 @@ export class AuthService {
|
|||||||
this.destroySessions().subscribe()
|
this.destroySessions().subscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUserAccessToken(): Promise<Token | undefined> {
|
deleteAccount() {
|
||||||
if (this.userAuthenticated) {
|
return this.deleteAccountRequest();
|
||||||
if ((!this.userAuthenticated.accessToken && this.refreshAccessToken) ||
|
}
|
||||||
(this.userAuthenticated.accessToken && this.userAuthenticated.accessToken.expirationDate < Date.now())) {
|
|
||||||
this.userAuthenticated = <User>(await this.refreshAccessToken());
|
addProfilePicture(file: File): void {
|
||||||
|
const fileType = file.type.split('/')[1];
|
||||||
|
this.getAddProfilePictureUrl(fileType).subscribe({
|
||||||
|
next: (url: string|null) => {
|
||||||
|
if (url != null) {
|
||||||
|
this.uploadProfilePicture(url, file).then(
|
||||||
|
(response: Observable<any>) => {
|
||||||
|
response.subscribe({
|
||||||
|
next: (response: any) => {
|
||||||
|
this.processProfilePicture().subscribe();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return this.userAuthenticated.accessToken;
|
})
|
||||||
} else return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private loginUser(userAuthAtempt: User): Observable<User|any> {
|
private loginUser(userAuthAtempt: User): Observable<User|any> {
|
||||||
@@ -155,6 +167,15 @@ export class AuthService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private deleteAccountRequest() {
|
||||||
|
let headers = this.createAuthorizationHeader()
|
||||||
|
|
||||||
|
return this.http.delete(
|
||||||
|
this.BACKEND_PATH + `/user/delete`,
|
||||||
|
{ headers: headers, withCredentials: true }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private validateUser(userAuthAtempt: Observable<User>) {
|
private validateUser(userAuthAtempt: Observable<User>) {
|
||||||
userAuthAtempt.pipe(
|
userAuthAtempt.pipe(
|
||||||
catchError(error => {
|
catchError(error => {
|
||||||
@@ -178,4 +199,67 @@ export class AuthService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getAddProfilePictureUrl(fileType: string): Observable<string|null> {
|
||||||
|
return this.http.post<{ presigned_url: string, file_key: string }>(
|
||||||
|
this.BACKEND_PATH + '/user/profile-picture?fileType=' + fileType,
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
headers: this.createAuthorizationHeader(),
|
||||||
|
withCredentials: true
|
||||||
|
}
|
||||||
|
).pipe(
|
||||||
|
first(),
|
||||||
|
map((res) => {
|
||||||
|
if (!!res && !!res.presigned_url) {
|
||||||
|
return res.presigned_url;
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private async uploadProfilePicture(url: string, file: File): Promise<Observable<any>> {
|
||||||
|
const fileData = await this.readAsArrayBuffer(file);
|
||||||
|
let headers = new HttpHeaders({
|
||||||
|
'Content-Type': file.type
|
||||||
|
})
|
||||||
|
return this.http.put(
|
||||||
|
url,
|
||||||
|
fileData,
|
||||||
|
{
|
||||||
|
headers: headers,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private processProfilePicture() {
|
||||||
|
return this.http.post(
|
||||||
|
this.BACKEND_PATH + '/user/profile-picture/proccess',
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
headers: this.createAuthorizationHeader(),
|
||||||
|
withCredentials: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private createAuthorizationHeader(): HttpHeaders {
|
||||||
|
return new HttpHeaders({
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': 'Bearer ' + this.userAuthenticated.accessToken?.token
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async readAsArrayBuffer(file: File): Promise<ArrayBuffer> {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.readAsArrayBuffer(file)
|
||||||
|
return new Promise<ArrayBuffer>((resolve, reject) => {
|
||||||
|
reader.onload = () => {
|
||||||
|
resolve(reader.result as ArrayBuffer);
|
||||||
|
};
|
||||||
|
reader.onerror = () => {
|
||||||
|
reject(reader.error);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import { Component, ElementRef, EventEmitter, Input, Output, ViewEncapsulation }
|
|||||||
animateChild(),
|
animateChild(),
|
||||||
{ optional: true }
|
{ optional: true }
|
||||||
),
|
),
|
||||||
animate('125ms ease-in')
|
animate('250ms ease-in')
|
||||||
])
|
])
|
||||||
),
|
),
|
||||||
transition(
|
transition(
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export const HttpError = t.iface([], {
|
|||||||
"timestamp": "string",
|
"timestamp": "string",
|
||||||
});
|
});
|
||||||
|
|
||||||
const HttpErrorTI: t.ITypeSuite = {
|
const exportedTypeSuite: t.ITypeSuite = {
|
||||||
HttpError,
|
HttpError,
|
||||||
};
|
};
|
||||||
export default HttpErrorTI;
|
export default exportedTypeSuite;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createCheckers } from "ts-interface-checker";
|
import { createCheckers } from "ts-interface-checker";
|
||||||
import HttpErrorTI from "./httpError.model-ti";
|
import HttpError from "./httpError.model-ti";
|
||||||
|
|
||||||
const HttpErrorChecker = createCheckers(HttpErrorTI)['HttpError'];
|
const HttpErrorChecker = createCheckers(HttpError)['HttpError'];
|
||||||
export default HttpErrorChecker;
|
export default HttpErrorChecker;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createCheckers } from "ts-interface-checker";
|
import { createCheckers } from "ts-interface-checker";
|
||||||
import TokenTI from "../token/token.model-ti";
|
import User from "./user.model-ti";
|
||||||
import UserTI from "./user.model-ti";
|
import Token from "../token/token.model-ti";
|
||||||
|
|
||||||
const UserChecker = createCheckers(UserTI, TokenTI)['User'];
|
const UserChecker = createCheckers(User, Token)['User'];
|
||||||
export default UserChecker;
|
export default UserChecker;
|
||||||
@@ -10,14 +10,13 @@ export const User = t.iface([], {
|
|||||||
"email": t.opt("string"),
|
"email": t.opt("string"),
|
||||||
"username": "string",
|
"username": "string",
|
||||||
"password": t.opt("string"),
|
"password": t.opt("string"),
|
||||||
|
"profilePictureUrl": t.opt("string"),
|
||||||
"accessToken": t.opt("Token"),
|
"accessToken": t.opt("Token"),
|
||||||
"refreshToken": t.opt("Token"),
|
"refreshToken": t.opt("Token"),
|
||||||
"authorities": t.opt(t.array(t.iface([], {
|
"roles": t.opt(t.array("string")),
|
||||||
"authority": "string",
|
|
||||||
}))),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const UserTI: t.ITypeSuite = {
|
const exportedTypeSuite: t.ITypeSuite = {
|
||||||
User,
|
User,
|
||||||
};
|
};
|
||||||
export default UserTI;
|
export default exportedTypeSuite;
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ export interface User {
|
|||||||
email?: string,
|
email?: string,
|
||||||
username: string,
|
username: string,
|
||||||
password?: string,
|
password?: string,
|
||||||
|
profilePictureUrl?: string,
|
||||||
accessToken?: Token,
|
accessToken?: Token,
|
||||||
refreshToken?: Token,
|
refreshToken?: Token,
|
||||||
authorities?: Array<{authority: string}>,
|
roles?: Array<string>,
|
||||||
validateAccessToken?: () => Token | undefined;
|
validateAccessToken?: () => Token | undefined;
|
||||||
};
|
};
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
<meta name="apple-mobile-web-app-title" content="Hideyoshi">
|
<meta name="apple-mobile-web-app-title" content="Hideyoshi">
|
||||||
|
|
||||||
<link rel="manifest" href="manifest.webmanifest">
|
<link rel="manifest" href="manifest.webmanifest">
|
||||||
|
<link rel="preconnect" href="https://hideyoshi-portfolio-dev.s3.amazonaws.com">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
/* You can add global styles to this file, and also import other style files */
|
/* You can add global styles to this file, and also import other style files */
|
||||||
|
|
||||||
@import 'font-montserrat.css';
|
@import 'font-montserrat.css';
|
||||||
|
|
||||||
* {
|
* {
|
||||||
|
|||||||
Reference in New Issue
Block a user