Merge pull request #15 from HideyoshiNakazone/devel

Devel - Implements Other Popups
This commit is contained in:
2023-08-28 01:04:59 -03:00
committed by GitHub
25 changed files with 2403 additions and 2441 deletions

4069
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -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",

View File

@@ -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>

View File

@@ -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 } 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 {User} from "../../shared/model/user/user.model"; import {User} from "../../shared/model/user/user.model";
import UserChecker from "../../shared/model/user/user.checker"; 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,12 +54,17 @@ 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(
res => { res => {
console.log(UserChecker.test(res));
if (res && UserChecker.test(res)) { if (res && UserChecker.test(res)) {
this.user = <User>res; this.user = <User>res;
} else { } else {
@@ -87,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();
} }
} }

View File

@@ -0,0 +1,7 @@
.help-container {
max-width: 400px;
}
p {
color: #555555;
}

View 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>

View 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();
});
});

View 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);
}
}

View 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%;
}
}

View File

@@ -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>

View File

@@ -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();
});
});

View 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);
}
}

View File

@@ -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>

View File

@@ -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();
});
});

View File

@@ -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;
}
}

View File

@@ -36,7 +36,6 @@ export class NavSliderComponent extends SliderItemComponent implements OnInit, O
ngOnInit(): void { ngOnInit(): void {
this.userSubscription = this.authService.authSubject.subscribe( this.userSubscription = this.authService.authSubject.subscribe(
res => { res => {
console.log(UserChecker.test(res));
if (res && UserChecker.test(res)) { if (res && UserChecker.test(res)) {
this.loggedUser = <User>res; this.loggedUser = <User>res;
} else { } else {

View File

@@ -11,18 +11,18 @@ import { User } from 'src/app/shared/model/user/user.model';
styleUrls: ['./user-slider.component.css'] styleUrls: ['./user-slider.component.css']
}) })
export class UserSliderComponent extends SliderItemComponent implements OnInit { export class UserSliderComponent extends SliderItemComponent implements OnInit {
userlessOptions = [ userlessOptions = [
{ {
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,19 +52,24 @@ 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();
} }
ngOnInit() { ngOnInit() {
this.authSubscription = this.authSubscription =
this.authService.authSubject.subscribe( this.authService.authSubject.subscribe(
res => { res => {
if (UserChecker.test(res)) { if (UserChecker.test(res)) {
@@ -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();
} }

View File

@@ -31,7 +31,9 @@
[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()">
@@ -58,7 +60,9 @@
<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>

View File

@@ -6,6 +6,8 @@ import {AuthService} from "../shared/auth/auth.service";
import UserChecker from "../shared/model/user/user.checker"; import UserChecker from "../shared/model/user/user.checker";
import {User} from "../shared/model/user/user.model"; import {User} from "../shared/model/user/user.model";
import {Subscription} from "rxjs"; 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',
@@ -40,12 +42,15 @@ export class HeaderComponent implements OnInit, OnDestroy {
private signupComponent!: ComponentRef<SignupComponent>; private signupComponent!: ComponentRef<SignupComponent>;
private myProfileComponent!: ComponentRef<MyProfileComponent>;
private helpComponent!: ComponentRef<HelpComponent>;
constructor(private viewContainerRef: ViewContainerRef, private authService: AuthService) { } constructor(private viewContainerRef: ViewContainerRef, private authService: AuthService) { }
ngOnInit(): void { ngOnInit(): void {
this.userSubscription = this.authService.authSubject.subscribe( this.userSubscription = this.authService.authSubject.subscribe(
res => { res => {
console.log(UserChecker.test(res));
if (res && UserChecker.test(res)) { if (res && UserChecker.test(res)) {
this.loggedUser = <User>res; this.loggedUser = <User>res;
} else { } else {
@@ -83,23 +88,36 @@ export class HeaderComponent implements OnInit, OnDestroy {
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 {
@@ -148,6 +166,41 @@ export class HeaderComponent implements OnInit, OnDestroy {
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();
} }
@@ -156,15 +209,11 @@ export class HeaderComponent implements OnInit, OnDestroy {
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();
}
} }

View File

@@ -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,

View File

@@ -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);
};
});
}
} }

View File

@@ -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(

View File

@@ -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>
@@ -25,4 +26,4 @@
<noscript>Please enable JavaScript to continue using this application.</noscript> <noscript>Please enable JavaScript to continue using this application.</noscript>
</body> </body>
</html> </html>

View File

@@ -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';
* { * {
@@ -9,4 +8,4 @@
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
font-family: 'Montserrat', sans-serif; font-family: 'Montserrat', sans-serif;
} }