diff --git a/src/app/header/header-dropdown/header-dropdown.component.html b/src/app/header/header-dropdown/header-dropdown.component.html
index 83a5e37..978dbf5 100644
--- a/src/app/header/header-dropdown/header-dropdown.component.html
+++ b/src/app/header/header-dropdown/header-dropdown.component.html
@@ -26,13 +26,13 @@
-
+
My Profile
-
+
diff --git a/src/app/header/header-dropdown/header-dropdown.component.ts b/src/app/header/header-dropdown/header-dropdown.component.ts
index f1ac932..a77eba5 100644
--- a/src/app/header/header-dropdown/header-dropdown.component.ts
+++ b/src/app/header/header-dropdown/header-dropdown.component.ts
@@ -1,10 +1,12 @@
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 { Subscription } from 'rxjs';
import { AuthService } from 'src/app/shared/auth/auth.service';
import {User} from "../../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({
selector: 'app-header-dropdown',
@@ -52,12 +54,17 @@ export class HeaderDropdownComponent implements OnInit, OnDestroy {
@Output()
signupPopupState: EventEmitter = new EventEmitter();
- constructor(private authService: AuthService) { }
+ @Output()
+ helpPopupState: EventEmitter = new EventEmitter();
+
+ @Output()
+ myProfilePopupState: EventEmitter = new EventEmitter();
+
+ constructor(private viewContainerRef: ViewContainerRef, private authService: AuthService) { }
ngOnInit(): void {
this.userSubscription = this.authService.authSubject.subscribe(
res => {
- console.log(UserChecker.test(res));
if (res && UserChecker.test(res)) {
this.user = res;
} else {
@@ -87,8 +94,15 @@ export class HeaderDropdownComponent implements OnInit, OnDestroy {
this.signupPopupState.emit(true);
}
+ onMyProfileClicked() {
+ this.myProfilePopupState.emit(true);
+ }
+
+ onHelpClicked() {
+ this.helpPopupState.emit(true);
+ }
+
onLogout() {
this.authService.logout();
}
-
}
diff --git a/src/app/header/header-popup/help/help.component.css b/src/app/header/header-popup/help/help.component.css
new file mode 100644
index 0000000..512a98e
--- /dev/null
+++ b/src/app/header/header-popup/help/help.component.css
@@ -0,0 +1,7 @@
+.help-container {
+ max-width: 400px;
+}
+
+p {
+ color: #555555;
+}
diff --git a/src/app/header/header-popup/help/help.component.html b/src/app/header/header-popup/help/help.component.html
new file mode 100644
index 0000000..934a967
--- /dev/null
+++ b/src/app/header/header-popup/help/help.component.html
@@ -0,0 +1,28 @@
+
+
+
+
+
+ This is a simple example project to demonstrate
+ User Authentication and Authorization using
+ Spring Security
+ and
+ OAuth2 .
+
+
+
+ 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.
+
+
+
+ All data can be deleted by clicking the "Delete Account" button
+ on the "My Profile" option.
+
+
+
+
+
diff --git a/src/app/header/header-popup/help/help.component.spec.ts b/src/app/header/header-popup/help/help.component.spec.ts
new file mode 100644
index 0000000..ba4b24a
--- /dev/null
+++ b/src/app/header/header-popup/help/help.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { HelpComponent } from './help.component';
+
+describe('HelpComponent', () => {
+ let component: HelpComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ HelpComponent ]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(HelpComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/header/header-popup/help/help.component.ts b/src/app/header/header-popup/help/help.component.ts
new file mode 100644
index 0000000..62a522f
--- /dev/null
+++ b/src/app/header/header-popup/help/help.component.ts
@@ -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();
+
+ constructor() { }
+
+ onStateChange(state: boolean) {
+ this.stateChange.emit(state);
+ }
+}
diff --git a/src/app/header/header-popup/my-profile/my-profile.component.css b/src/app/header/header-popup/my-profile/my-profile.component.css
new file mode 100644
index 0000000..2a0cc09
--- /dev/null
+++ b/src/app/header/header-popup/my-profile/my-profile.component.css
@@ -0,0 +1,98 @@
+* {
+ margin: 0;
+}
+
+.profile-container .row {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+}
+
+.profile-container {
+ justify-content: space-around;
+ display: flex !important;
+ flex-direction: column;
+ align-content: center;
+ align-items: center;
+ height: 200px;
+ max-width: 400px;
+}
+
+.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-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-container {
+ all: unset;
+ justify-content: space-around;
+ width: 600px;
+ align-items: center;
+ display: flex;
+ height: 200px;
+ }
+
+ .profile-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%;
+ }
+
+}
diff --git a/src/app/header/header-popup/my-profile/my-profile.component.html b/src/app/header/header-popup/my-profile/my-profile.component.html
new file mode 100644
index 0000000..eda3afd
--- /dev/null
+++ b/src/app/header/header-popup/my-profile/my-profile.component.html
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Add Profile Picture
+
+
+
+
+
+
+
+ Delete Account
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/header/header-popup/my-profile/my-profile.component.spec.ts b/src/app/header/header-popup/my-profile/my-profile.component.spec.ts
new file mode 100644
index 0000000..3141cca
--- /dev/null
+++ b/src/app/header/header-popup/my-profile/my-profile.component.spec.ts
@@ -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;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ MyProfileComponent ]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(MyProfileComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/header/header-popup/my-profile/my-profile.component.ts b/src/app/header/header-popup/my-profile/my-profile.component.ts
new file mode 100644
index 0000000..8bcfdd5
--- /dev/null
+++ b/src/app/header/header-popup/my-profile/my-profile.component.ts
@@ -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";
+
+
+@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();
+
+ alterForm!: FormGroup;
+
+ errorMessage!: string | null;
+
+ isShowErrorMessage = false;
+
+ 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);
+ }
+
+ public showErrorMessage(): string {
+ if (this.isShowErrorMessage) {
+ return "show";
+ }
+ return "hide";
+ }
+
+ public hideErrorMessage(): string {
+ if (!!this.errorMessage) {
+ return "hide";
+ }
+ return "show";
+ }
+
+ public onDeleteAccount() {
+ this.authService.deleteAccount().subscribe({
+ next: (res) => {
+ if (res && UserChecker.test(res)) {
+ this.closePopup()
+ } if (HttpErrorChecker.test(res)) {
+ this.errorMessage = (res).details;
+ }
+ }
+ })
+ // this.authService.logout()
+ // this.onStateChange(false);
+ }
+
+ public onAddProfilePicture() {
+ this.authService.addProfilePicture()
+ }
+
+ hideAuthContainer(event: any) {
+ if (event.toState === "hide") {
+ event.element.style.display = "none";
+ this.isShowErrorMessage = true;
+ }
+ }
+
+ private closePopup() {
+ this.onStateChange(false);
+ }
+
+}
diff --git a/src/app/header/header-slider/nav-slider/nav-slider.component.ts b/src/app/header/header-slider/nav-slider/nav-slider.component.ts
index 5b702ac..104595b 100644
--- a/src/app/header/header-slider/nav-slider/nav-slider.component.ts
+++ b/src/app/header/header-slider/nav-slider/nav-slider.component.ts
@@ -36,7 +36,6 @@ export class NavSliderComponent extends SliderItemComponent implements OnInit, O
ngOnInit(): void {
this.userSubscription = this.authService.authSubject.subscribe(
res => {
- console.log(UserChecker.test(res));
if (res && UserChecker.test(res)) {
this.loggedUser = res;
} else {
diff --git a/src/app/header/header-slider/user-slider/user-slider.component.ts b/src/app/header/header-slider/user-slider/user-slider.component.ts
index aef62f5..7a62574 100644
--- a/src/app/header/header-slider/user-slider/user-slider.component.ts
+++ b/src/app/header/header-slider/user-slider/user-slider.component.ts
@@ -11,18 +11,18 @@ import { User } from 'src/app/shared/model/user/user.model';
styleUrls: ['./user-slider.component.css']
})
export class UserSliderComponent extends SliderItemComponent implements OnInit {
-
+
userlessOptions = [
{
name: "Login",
onClick: () => {
- this.loginOptionClicked();
+ this.onLoginOptionClicked();
}
},
{
name: "Signup",
onClick: () => {
- this.signupOptionClicked();
+ this.onSignUpOptionClick();
}
}
]
@@ -30,11 +30,15 @@ export class UserSliderComponent extends SliderItemComponent implements OnInit {
userOptions = [
{
name: "My Profile",
- onClick: () => {}
+ onClick: () => {
+ this.onMyProfileClicked()
+ }
},
{
name: "Help",
- onClick: () => {}
+ onClick: () => {
+ this.onHelpClicked();
+ }
},
{
name: "Logout",
@@ -48,19 +52,24 @@ export class UserSliderComponent extends SliderItemComponent implements OnInit {
authSubscription!: Subscription;
+ @Output()
+ loginPopupState: EventEmitter = new EventEmitter();
@Output()
- loginPopupState = new EventEmitter();
+ signupPopupState: EventEmitter = new EventEmitter();
@Output()
- signupPopupState = new EventEmitter();
+ helpPopupState: EventEmitter = new EventEmitter();
+
+ @Output()
+ myProfilePopupState: EventEmitter = new EventEmitter();
constructor(private authService: AuthService) {
super();
}
ngOnInit() {
- this.authSubscription =
+ this.authSubscription =
this.authService.authSubject.subscribe(
res => {
if (UserChecker.test(res)) {
@@ -72,14 +81,22 @@ export class UserSliderComponent extends SliderItemComponent implements OnInit {
)
}
- loginOptionClicked(): void {
+ onLoginOptionClicked(): void {
this.loginPopupState.emit(true);
}
- signupOptionClicked(): void {
+ onSignUpOptionClick(): void {
this.signupPopupState.emit(true);
}
+ onMyProfileClicked(): void {
+ this.myProfilePopupState.emit(true);
+ }
+
+ onHelpClicked(): void {
+ this.helpPopupState.emit(true);
+ }
+
onLogout() {
this.authService.logout();
}
diff --git a/src/app/header/header.component.html b/src/app/header/header.component.html
index 51edca0..72ba43a 100644
--- a/src/app/header/header.component.html
+++ b/src/app/header/header.component.html
@@ -31,7 +31,9 @@
[ignoreClickOutside]="[profileBtn]"
[state]="profileDropdownState"
(loginPopupState)="loginPopupStateChange($event)"
- (signupPopupState)="signupPopupStateChange($event)">
+ (signupPopupState)="signupPopupStateChange($event)"
+ (myProfilePopupState)="myProfilePopupStateChange($event)"
+ (helpPopupState)="helpPopupStateChange($event)">
@@ -58,7 +60,9 @@
+ (signupPopupState)="signupPopupStateChange($event)"
+ (myProfilePopupState)="myProfilePopupStateChange($event)"
+ (helpPopupState)="helpPopupStateChange($event)">
diff --git a/src/app/header/header.component.ts b/src/app/header/header.component.ts
index 84086aa..0400e74 100644
--- a/src/app/header/header.component.ts
+++ b/src/app/header/header.component.ts
@@ -6,6 +6,8 @@ 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({
selector: 'app-header',
@@ -40,12 +42,15 @@ export class HeaderComponent implements OnInit, OnDestroy {
private signupComponent!: ComponentRef;
+ private myProfileComponent!: ComponentRef;
+
+ private helpComponent!: ComponentRef;
+
constructor(private viewContainerRef: ViewContainerRef, private authService: AuthService) { }
ngOnInit(): void {
this.userSubscription = this.authService.authSubject.subscribe(
res => {
- console.log(UserChecker.test(res));
if (res && UserChecker.test(res)) {
this.loggedUser = res;
} else {
@@ -83,23 +88,36 @@ export class HeaderComponent implements OnInit, OnDestroy {
public closeDropdown(): void {
this.profileDropdownState = false;
}
-
- public closeNavSlider(): void {
- if (this.userSliderStatus) {
- this.userSliderStatus = false;
- } else {
- this.navSliderStatus = false;
- }
- }
-
public loginPopupStateChange(state: boolean): void {
-
if (state) {
this.createLoginPopup();
} else {
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 {
@@ -148,6 +166,41 @@ export class HeaderComponent implements OnInit, OnDestroy {
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() {
this.loginComponent.destroy();
}
@@ -156,15 +209,11 @@ export class HeaderComponent implements OnInit, OnDestroy {
this.signupComponent.destroy();
}
- public signupPopupStateChange(state: boolean): void {
- this.signupPopupState = state;
-
- if (state) {
- this.createSignupPopup();
- } else {
- this.closeSignupPopup();
- }
-
+ private closeMyProfilePopup() {
+ this.myProfileComponent.destroy();
}
+ private closeHelpPopup() {
+ this.helpComponent.destroy();
+ }
}
diff --git a/src/app/header/header.module.ts b/src/app/header/header.module.ts
index 28e1946..85d6a0c 100644
--- a/src/app/header/header.module.ts
+++ b/src/app/header/header.module.ts
@@ -16,6 +16,8 @@ import { SignupComponent } from './header-popup/signup/signup.component';
import { CallbackComponent } from './header-popup/callback/callback.component';
import {MatIconModule} from '@angular/material/icon';
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';
@@ -30,6 +32,8 @@ import { ErrorBoxComponent } from './header-popup/error-box/error-box.component'
SignupComponent,
CallbackComponent,
ErrorBoxComponent,
+ HelpComponent,
+ MyProfileComponent,
],
imports: [
CommonModule,
diff --git a/src/app/shared/auth/auth.service.ts b/src/app/shared/auth/auth.service.ts
index 178d534..4197b12 100644
--- a/src/app/shared/auth/auth.service.ts
+++ b/src/app/shared/auth/auth.service.ts
@@ -21,7 +21,6 @@ export class AuthService {
readonly BACKEND_OAUTH_PATH = environment.backendOAuthPath;
constructor(private http: HttpClient) { }
-
login(userAuthAtempt: User): void {
this.validateUser(this.loginUser(userAuthAtempt));
}
@@ -56,16 +55,15 @@ export class AuthService {
this.destroySessions().subscribe()
}
- async getUserAccessToken(): Promise {
- if (this.userAuthenticated) {
- if ((!this.userAuthenticated.accessToken && this.refreshAccessToken) ||
- (this.userAuthenticated.accessToken && this.userAuthenticated.accessToken.expirationDate < Date.now())) {
- this.userAuthenticated = (await this.refreshAccessToken());
- }
- return this.userAuthenticated.accessToken;
- } else return
+ deleteAccount() {
+ return this.deleteAccountRequest().pipe(
+ first()
+ );
}
+ addProfilePicture() {
+
+ }
private loginUser(userAuthAtempt: User): Observable {
let loginParams = new URLSearchParams();
@@ -155,6 +153,13 @@ export class AuthService {
);
}
+ private deleteAccountRequest() {
+ return this.http.delete(
+ this.BACKEND_PATH + `/user/delete/${this.userAuthenticated.id}`,
+ { withCredentials: true }
+ );
+ }
+
private validateUser(userAuthAtempt: Observable) {
userAuthAtempt.pipe(
catchError(error => {
@@ -177,5 +182,4 @@ export class AuthService {
}
});
}
-
}
diff --git a/src/app/shared/components/popup/popup.component.ts b/src/app/shared/components/popup/popup.component.ts
index 24502f2..3cacb5e 100644
--- a/src/app/shared/components/popup/popup.component.ts
+++ b/src/app/shared/components/popup/popup.component.ts
@@ -21,7 +21,7 @@ import { Component, ElementRef, EventEmitter, Input, Output, ViewEncapsulation }
animateChild(),
{ optional: true }
),
- animate('125ms ease-in')
+ animate('250ms ease-in')
])
),
transition(
diff --git a/src/index.html b/src/index.html
index 53e6755..9208300 100644
--- a/src/index.html
+++ b/src/index.html
@@ -18,6 +18,7 @@
+
@@ -25,4 +26,4 @@
Please enable JavaScript to continue using this application.
-