Adds Local Profile Pictures Implementation
This commit is contained in:
4071
package-lock.json
generated
4071
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",
|
||||||
|
|||||||
@@ -16,10 +16,9 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
||||||
<div class="btn-container">
|
<div class="btn-container">
|
||||||
<button class="picture-btn"
|
<app-profile-picture-picker
|
||||||
(click)="onAddProfilePicture()">
|
(imageSent)="onProfilePictureSent($event)">
|
||||||
Add Profile Picture
|
</app-profile-picture-picker>
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="separator-line">
|
<div class="separator-line">
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {first, take} from "rxjs";
|
|||||||
import UserChecker from "../../../shared/model/user/user.checker";
|
import UserChecker from "../../../shared/model/user/user.checker";
|
||||||
import HttpErrorChecker from "../../../shared/model/httpError/httpErrorChecker";
|
import HttpErrorChecker from "../../../shared/model/httpError/httpErrorChecker";
|
||||||
import {HttpError} from "../../../shared/model/httpError/httpError.model";
|
import {HttpError} from "../../../shared/model/httpError/httpError.model";
|
||||||
|
import {faFileUpload} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -101,6 +102,8 @@ export class MyProfileComponent implements OnInit {
|
|||||||
|
|
||||||
isShowErrorMessage = false;
|
isShowErrorMessage = false;
|
||||||
|
|
||||||
|
_fileIcon = faFileUpload
|
||||||
|
|
||||||
constructor(private authService: AuthService) {
|
constructor(private authService: AuthService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,36 +119,27 @@ export class MyProfileComponent implements OnInit {
|
|||||||
this.stateChange.emit(state);
|
this.stateChange.emit(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public showErrorMessage(): string {
|
showErrorMessage(): string {
|
||||||
if (this.isShowErrorMessage) {
|
if (this.isShowErrorMessage) {
|
||||||
return "show";
|
return "show";
|
||||||
}
|
}
|
||||||
return "hide";
|
return "hide";
|
||||||
}
|
}
|
||||||
|
|
||||||
public hideErrorMessage(): string {
|
hideErrorMessage(): string {
|
||||||
if (!!this.errorMessage) {
|
if (!!this.errorMessage) {
|
||||||
return "hide";
|
return "hide";
|
||||||
}
|
}
|
||||||
return "show";
|
return "show";
|
||||||
}
|
}
|
||||||
|
|
||||||
public onDeleteAccount() {
|
onDeleteAccount() {
|
||||||
this.authService.deleteAccount().subscribe({
|
this.authService.deleteAccount().subscribe({
|
||||||
next: (res) => {
|
next: (response: any) => {
|
||||||
if (res && UserChecker.test(res)) {
|
this.authService.logout();
|
||||||
this.closePopup()
|
|
||||||
} if (HttpErrorChecker.test(res)) {
|
|
||||||
this.errorMessage = (<HttpError>res).details;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// this.authService.logout()
|
this.closePopup();
|
||||||
// this.onStateChange(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public onAddProfilePicture() {
|
|
||||||
this.authService.addProfilePicture()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hideAuthContainer(event: any) {
|
hideAuthContainer(event: any) {
|
||||||
@@ -155,6 +149,12 @@ export class MyProfileComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onProfilePictureSent(event: any) {
|
||||||
|
if (event) {
|
||||||
|
this.closePopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private closePopup() {
|
private closePopup() {
|
||||||
this.onStateChange(false);
|
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">
|
||||||
|
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,35 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
get isProfilePictureSelected(): boolean {
|
||||||
|
return !this.profilePicture;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ 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 { HelpComponent } from './header-popup/help/help.component';
|
||||||
import { MyProfileComponent } from './header-popup/my-profile/my-profile.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';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -34,6 +35,7 @@ import { MyProfileComponent } from './header-popup/my-profile/my-profile.compone
|
|||||||
ErrorBoxComponent,
|
ErrorBoxComponent,
|
||||||
HelpComponent,
|
HelpComponent,
|
||||||
MyProfileComponent,
|
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'
|
||||||
@@ -56,14 +56,28 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deleteAccount() {
|
deleteAccount() {
|
||||||
return this.deleteAccountRequest().pipe(
|
return this.deleteAccountRequest();
|
||||||
first()
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
addProfilePicture() {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private loginUser(userAuthAtempt: User): Observable<User|any> {
|
private loginUser(userAuthAtempt: User): Observable<User|any> {
|
||||||
|
|
||||||
let loginParams = new URLSearchParams();
|
let loginParams = new URLSearchParams();
|
||||||
@@ -154,9 +168,11 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private deleteAccountRequest() {
|
private deleteAccountRequest() {
|
||||||
|
let headers = this.createAuthorizationHeader()
|
||||||
|
|
||||||
return this.http.delete(
|
return this.http.delete(
|
||||||
this.BACKEND_PATH + `/user/delete/${this.userAuthenticated.id}`,
|
this.BACKEND_PATH + `/user/delete`,
|
||||||
{ withCredentials: true }
|
{ headers: headers, withCredentials: true }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,4 +198,68 @@ 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);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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