aboutsummaryrefslogtreecommitdiff
path: root/src/app/components/profile-settings
diff options
context:
space:
mode:
Diffstat (limited to 'src/app/components/profile-settings')
-rw-r--r--src/app/components/profile-settings/profile-settings.component.css124
-rw-r--r--src/app/components/profile-settings/profile-settings.component.html116
-rw-r--r--src/app/components/profile-settings/profile-settings.component.ts307
3 files changed, 547 insertions, 0 deletions
diff --git a/src/app/components/profile-settings/profile-settings.component.css b/src/app/components/profile-settings/profile-settings.component.css
new file mode 100644
index 0000000..1c07d9f
--- /dev/null
+++ b/src/app/components/profile-settings/profile-settings.component.css
@@ -0,0 +1,124 @@
+* {
+ box-sizing: border-box;
+}
+
+#content {
+ max-width: 22em;
+ justify-content: start;
+}
+
+form {
+ width: 100%;
+}
+
+hr {
+ width: calc(100% - 1em);
+ color: black;
+ border: 1px solid black;
+}
+
+/* Navigation bar (for loggedin user) */
+
+#navigation {
+ display: flex;
+ width: 100%;
+}
+
+#navigation > .submit-btn {
+ flex: 1;
+ margin-top: 0;
+ margin-left: .5em;
+ font-size: inherit;
+}
+
+#navigation > .submit-btn:nth-of-type(1) {
+ margin-left: 0;
+}
+
+/* Form */
+
+#update-profile-picture {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0 .5em;
+ flex-wrap: wrap;
+}
+
+#profile-picture {
+ width: 5em;
+ height: 5em;
+}
+
+#submit-file {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 1em;
+}
+
+#upload-file:hover {
+ cursor: inherit;
+}
+
+#upload-file > input:hover {
+ cursor: pointer;
+}
+
+#upload-file > input::-webkit-file-upload-button {
+ visibility: hidden;
+}
+
+#update-user {
+ margin-top: 1.1em;
+}
+
+.input-field {
+ border-color: var(--focus-color) !important;
+ caret-color: var(--focus-color);
+ border-width: 2px !important;
+ margin-top: -1px !important;
+}
+
+.input-field-label {
+ font-size: .7em;
+ color: var(--focus-color);
+ transform: translate(0, -1.2em);
+}
+
+#all-languages, #all-technologies {
+ display: flex;
+ flex-wrap: wrap;
+}
+
+/* Buttons */
+
+.edit-btn {
+ border-radius: 0 !important;
+ color: var(--focus-color);
+ background-color: white;
+ border-color: var(--focus-color);
+}
+
+.edit-btn:hover {
+ color: white;
+ background-color: black;
+ border-color: black !important;
+}
+
+.submit-btn {
+ margin-bottom: .5em;
+}
+
+#update-profile-btn {
+ margin-top: 1em;
+}
+
+#confirm-delete {
+ box-sizing: border-box;
+ width: 100%;
+ background-color: var(--failure);
+ color: white;
+ padding: .2em;
+ text-align: center;
+}
diff --git a/src/app/components/profile-settings/profile-settings.component.html b/src/app/components/profile-settings/profile-settings.component.html
new file mode 100644
index 0000000..502697d
--- /dev/null
+++ b/src/app/components/profile-settings/profile-settings.component.html
@@ -0,0 +1,116 @@
+<app-loading *ngIf="!dataArrived"></app-loading>
+
+<div id="content" *ngIf="dataArrived">
+ <nav id="navigation">
+ <button class="submit-btn" (click)="goToProfile()">ᐊ Back</button>
+ <button class="submit-btn" (click)="navigateToAdminPanel()" *ngIf="isAdminUser">Panel</button>
+ <button class="submit-btn" (click)="logout()">Logout</button>
+ </nav>
+ <hr>
+ <div class="scroll-standalone">
+ <form id="update-profile-picture" [formGroup]="updateProfilePictureFormGroup" (ngSubmit)="updateProfilePicture()">
+ <img id="profile-picture" class="round-image" [src]="user.profilePictureURL">
+ <div id="submit-file">
+ <div id="upload-file" class="submit-btn">
+ <input type="file" accept="image/*" formControlName="fileUpload" (change)="onFileUpload($event)">
+ </div>
+ <button class="submit-btn" type="submit">Update profile picture</button>
+ </div>
+ </form>
+ <hr>
+ <form id="update-user" [formGroup]="updateUserFormGroup" (ngSubmit)="onSubmit()">
+ <div class="input-selection">
+ <input type="text" class="input-field" formControlName="firstName" required>
+ <label class="input-field-label">First Name</label>
+
+ <div class="input-errors">
+ <label *ngIf="updateUserFormGroup.get('firstName')?.errors?.required" class="error">*Required</label>
+ <label *ngIf="updateUserFormGroup.get('firstName')?.errors?.minlength" class="error">*Minimum 3 characters</label>
+ </div>
+ </div>
+
+ <div class="input-selection">
+ <input type="text" class="input-field" formControlName="lastName" required>
+ <label class="input-field-label">Last Name</label>
+
+ <div class="input-errors">
+ <label *ngIf="updateUserFormGroup.get('lastName')?.errors?.required" class="error">*Required</label>
+ <label *ngIf="updateUserFormGroup.get('lastName')?.errors?.minlength" class="error">*Minimum 3 characters</label>
+ </div>
+ </div>
+
+ <div class="input-selection">
+ <input type="text" class="input-field" formControlName="username" required>
+ <label class="input-field-label">Username</label>
+
+ <div class="input-errors">
+ <label *ngIf="updateUserFormGroup.get('username')?.errors?.required" class="error">*Required</label>
+ <label *ngIf="updateUserFormGroup.get('username')?.errors?.minlength" class="error">*Minimum 3 characters</label>
+ </div>
+ </div>
+
+ <div class="input-selection">
+ <input type="text" class="input-field" formControlName="email" required>
+ <label class="input-field-label">Email</label>
+
+ <div class="input-errors">
+ <label *ngIf="updateUserFormGroup.get('email')?.errors?.required" class="error">*Required</label>
+ <label *ngIf="updateUserFormGroup.get('email')?.errors?.email" class="error">*Invalid email</label>
+ </div>
+ </div>
+
+ <div class="input-selection">
+ <input type="password" class="input-field" formControlName="password" required>
+ <label class="input-field-label">Password</label>
+
+ <div class="input-errors">
+ <label *ngIf="updateUserFormGroup.get('password')?.errors?.required" class="error">*Required</label>
+ <label *ngIf="updateUserFormGroup.get('password')?.errors?.minlength" class="error">*Minimum 3 characters</label>
+ <label *ngIf="updateUserFormGroup.get('password')?.errors?.pattern" class="error">*At least 1 number</label>
+ </div>
+ </div>
+ <button type="button" class="submit-btn edit-btn" (click)="toggleLanguages()">▼ Edit Languages ▼</button>
+ <div *ngIf="showLanguages">
+ <div class="input-selection">
+ <input type="text" class="input-field" formControlName="languageInput" required>
+
+ <div class="input-errors">
+ <label class="error">Type in your desired languages, separated by a space</label>
+ </div>
+ </div>
+ Available languages:
+ <div id="all-languages">
+ <div class="user-language" *ngFor="let lang of availableLanguages">
+ {{ lang.name }}
+ </div>
+ </div>
+ </div>
+
+ <button type="button" class="submit-btn edit-btn" (click)="toggleTechnologies()">▼ Edit Technologies ▼</button>
+ <div *ngIf="showTechnologies">
+ <div class="input-selection">
+ <input type="text" class="input-field" formControlName="technologyInput" required>
+
+ <div class="input-errors">
+ <label class="error">Type in your desired technologies, separated by a space</label>
+ </div>
+ </div>
+ Available technologies:
+ <div id="all-technologies">
+ <div class="user-technology" *ngFor="let tech of availableTechnologies">
+ {{ tech.name }}
+ </div>
+ </div>
+ </div>
+
+ <button id="update-profile-btn" class="submit-btn" type="submit">Update profile</button>
+ <app-success-bar></app-success-bar>
+ <app-error-bar></app-error-bar>
+ </form>
+ <hr>
+ <div id="confirm-delete" *ngIf="deleteAccountConfirm">
+ Are you sure you want to delete your account?<br>This is permanent!
+ </div>
+ <button class="submit-btn delete-btn" (click)="deleteAccount()">Delete account</button>
+ </div>
+</div>
diff --git a/src/app/components/profile-settings/profile-settings.component.ts b/src/app/components/profile-settings/profile-settings.component.ts
new file mode 100644
index 0000000..a484665
--- /dev/null
+++ b/src/app/components/profile-settings/profile-settings.component.ts
@@ -0,0 +1,307 @@
+import { Location } from '@angular/common';
+import { HttpErrorResponse } from '@angular/common/http';
+import { Component, OnInit, ViewChild } from '@angular/core';
+import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
+import { Router } from '@angular/router';
+import { LanguageService } from 'src/app/services/language.service';
+import { UserService } from 'src/app/services/user.service';
+import { TechnologyService } from 'src/app/services/technology.service';
+import { User } from 'src/models/identity/user';
+import { ErrorBarComponent } from '../error-bar/error-bar.component';
+import { SuccessBarComponent } from '../success-bar/success-bar.component';
+import { Language } from 'src/models/language';
+import { Technology } from 'src/models/technology';
+import { TokenService } from 'src/app/services/token.service';
+import { Title } from '@angular/platform-browser';
+import { AppConstants } from 'src/app/app-constants.module';
+
+@Component({
+ selector: 'app-profile-settings',
+ templateUrl: './profile-settings.component.html',
+ styleUrls: ['./profile-settings.component.css']
+})
+export class ProfileSettingsComponent implements OnInit {
+ private _title = 'Profile Settings';
+ @ViewChild(ErrorBarComponent) private _errorBar: ErrorBarComponent;
+ @ViewChild(SuccessBarComponent) private _successBar: SuccessBarComponent;
+ private _urlUsername: string;
+ public isAdminUser = false;
+ public dataArrived = false;
+ public deleteAccountConfirm = false;
+ public showLanguages = false;
+ public showTechnologies = false;
+ public updateUserFormGroup: FormGroup;
+ public updateProfilePictureFormGroup: FormGroup;
+ public newProfilePicture: File;
+ public user: User;
+ public availableLanguages: Language[];
+ public availableTechnologies: Technology[];
+
+ constructor(private _titleService: Title, private _router: Router, private _userService: UserService, private _languageService: LanguageService, private _technologyService: TechnologyService, private _tokenService: TokenService, private _fb: FormBuilder, private _location: Location) {
+ this._titleService.setTitle(this._title);
+ }
+
+ ngOnInit(): void {
+ this._urlUsername = this._router.url.substring(9);
+ this._urlUsername = this._urlUsername.substring(0, this._urlUsername.length - 9);
+
+ this.user = this._userService.getDefaultUser();
+ this.availableLanguages = [];
+ this.availableTechnologies = [];
+ this.newProfilePicture = new File([], '');
+
+ this._userService.getUserByUsernameRequest(this._urlUsername).subscribe(
+ (res: object) => {
+ Object.assign(this.user, res);
+ this.isAdminUser = this.user.roles.map(x => x.name).includes(AppConstants.ADMIN_ROLE_NAME);
+ this.finishUserLoading();
+ },
+ (err: HttpErrorResponse) => {
+ this._router.navigate(['/not-found']);
+ }
+ );
+
+ this._languageService.getAllLanguagesWithSessionStorageRequest().subscribe(
+ (result: object) => {
+ this.availableLanguages = result as Language[];
+ }
+ );
+ this._technologyService.getAllTechnologiesWithSessionStorageRequest().subscribe(
+ (result: object) => {
+ this.availableTechnologies = result as Technology[];
+ }
+ );
+ }
+
+ private finishUserLoading(): void {
+ if (sessionStorage.getItem('UserCred')) {
+ const userFromToken: User = this._userService.getDefaultUser();
+
+ this._userService.getUserFromSessionStorageRequest().subscribe(
+ (tokenRes: object) => {
+ Object.assign(userFromToken, tokenRes);
+
+ if (userFromToken.userName === this._urlUsername) {
+ this.initForms();
+ this.dataArrived = true;
+ }
+ else {
+ this.goToProfile();
+ }
+ },
+ (err: HttpErrorResponse) => {
+ this.logout();
+ }
+ );
+ }
+ else {
+ this.goToProfile();
+ }
+ }
+
+ private initForms(): void {
+ this.updateUserFormGroup = this._fb.group({
+ firstName: new FormControl(this.user.firstName, [
+ Validators.required,
+ Validators.minLength(3)
+ ]),
+ lastName: new FormControl(this.user.lastName, [
+ Validators.required,
+ Validators.minLength(3)
+ ]),
+ username: new FormControl(this.user.userName, [
+ Validators.required,
+ Validators.minLength(3)
+ ]),
+ email: new FormControl(this.user.email, [
+ Validators.required,
+ Validators.email,
+ ]),
+ password: new FormControl('', [
+ Validators.required,
+ Validators.minLength(3),
+ Validators.pattern('.*[0-9].*') // Check if password contains atleast one number
+ ]),
+
+ // For language we have two different controls,
+ // the first one is used for input, the other one for sending data
+ // because if we edit the control for input,
+ // we're also gonna change the input field in the HTML
+ languageInput: new FormControl(''), // The one for input
+ languages: new FormControl(''), // The one that is sent
+
+ // For technologies it's the same as it is with languages
+ technologyInput: new FormControl(''),
+ technologies: new FormControl('')
+ });
+
+ this.getLanguagesForShowing().then(value => {
+ this.updateUserFormGroup.patchValue({ languageInput : value });
+ });
+
+ this.getTechnologiesForShowing().then(value => {
+ this.updateUserFormGroup.patchValue({ technologyInput : value });
+ });
+
+ this.updateProfilePictureFormGroup = this._fb.group({
+ fileUpload: new FormControl('')
+ });
+
+ this.updateUserFormGroup.valueChanges.subscribe(() => {
+ this._successBar?.hideMsg();
+ this._errorBar?.hideError();
+ });
+ }
+
+ private getLanguagesForShowing(): Promise<string> {
+ return new Promise(resolve => {
+ this._languageService.getFullLanguagesFromIncomplete(this.user.languages).then(value => {
+ this.user.languages = value;
+ resolve(value.map(x => x.name).join(' '));
+ });
+ });
+ }
+
+ private getTechnologiesForShowing(): Promise<string> {
+ return new Promise(resolve => {
+ this._technologyService.getFullTechnologiesFromIncomplete(this.user.technologies).then(value => {
+ this.user.technologies = value;
+ resolve(value.map(x => x.name).join(' '));
+ });
+ });
+ }
+
+ onFileUpload(event: any): void {
+ this.newProfilePicture = event.target.files[0];
+ }
+
+ updateProfilePicture(): void {
+ if (this.newProfilePicture.size === 0) {
+ return;
+ }
+
+ this._userService.putProfilePictureFromSessionStorageRequest(this.newProfilePicture).subscribe(
+ (result: object) => {
+ this.reloadPage();
+ }
+ );
+ this.dataArrived = false;
+ }
+
+ onSubmit(): void {
+ this._successBar.hideMsg();
+ this._errorBar.hideError();
+
+ this.patchLanguagesControl();
+ this.patchTechnologiesControl();
+
+ this._userService.putUserFromSessionStorageRequest(this.updateUserFormGroup, this.user.roles, this.user.friends).subscribe(
+ (result: object) => {
+ this._successBar.showMsg('Profile updated successfully!');
+ },
+ (err: HttpErrorResponse) => {
+ this._errorBar.showError(err);
+ }
+ );
+ }
+
+ private patchLanguagesControl(): void {
+ // Get user input
+ const langControl = this.updateUserFormGroup.get('languageInput')?.value as string ?? '';
+
+ if (langControl === '') {
+ // Add the data to the form (to the value that is going to be sent)
+ this.updateUserFormGroup.patchValue({
+ languages : []
+ });
+ }
+ else {
+ const names = langControl.split(' ');
+
+ // Transfer user input to objects of type { "name": "value" }
+ const actualLanguages = [];
+ for (const lName of names) {
+ if (lName !== '') {
+ actualLanguages.push({ name : lName });
+ }
+ }
+
+ // Add the data to the form (to the value that is going to be sent)
+ this.updateUserFormGroup.patchValue({
+ languages : actualLanguages
+ });
+ }
+ }
+
+ private patchTechnologiesControl(): void {
+ // Get user input
+ const techControl = this.updateUserFormGroup.get('technologyInput')?.value as string ?? '';
+
+ if (techControl === '') {
+ // Add the data to the form (to the value that is going to be sent)
+ this.updateUserFormGroup.patchValue({
+ technologies : []
+ });
+ }
+ else {
+ const names = techControl.split(' ');
+
+ // Transfer user input to objects of type { "name": "value" }
+ const actualTechnologies = [];
+ for (const tName of names) {
+ if (tName !== '') {
+ actualTechnologies.push({ name : tName });
+ }
+ }
+
+ // Add the data to the form (to the value that is going to be sent)
+ this.updateUserFormGroup.patchValue({
+ technologies : actualTechnologies
+ });
+ }
+ }
+
+ goToProfile(): void {
+ this._router.navigate([this._router.url.substring(0, this._router.url.length - 9)]);
+ }
+
+ navigateToAdminPanel(): void {
+ this._router.navigate(['/admin-panel']);
+ }
+
+ logout(): void {
+ this._tokenService.logoutUserFromSessionStorage();
+ this.goToProfile();
+ }
+
+ toggleLanguages(): void {
+ this.showLanguages = !this.showLanguages;
+ }
+
+ toggleTechnologies(): void {
+ this.showTechnologies = !this.showTechnologies;
+ }
+
+ deleteAccount(): void {
+ if (this.deleteAccountConfirm) {
+ this._userService.deleteUserFromSessionStorageRequest().subscribe(
+ (res: object) => {
+ this.logout();
+ },
+ (err: HttpErrorResponse) => {
+ this._errorBar.showError(err);
+ }
+ );
+ this.dataArrived = false;
+ }
+ else {
+ this.deleteAccountConfirm = true;
+ }
+ }
+
+ private reloadPage(): void {
+ this._router.routeReuseStrategy.shouldReuseRoute = () => false;
+ this._router.onSameUrlNavigation = 'reload';
+ this._router.navigate([this._router.url]);
+ }
+}