Running Prettier in Project

This commit is contained in:
2023-10-14 19:17:28 -03:00
parent 3bdc66f8fb
commit b1b90f10d7
100 changed files with 18174 additions and 17212 deletions

View File

@@ -1,54 +1,41 @@
{ {
"root": true, "root": true,
"ignorePatterns": [ "ignorePatterns": ["projects/**/*"],
"projects/**/*" "overrides": [
], {
"overrides": [ "files": ["*.ts"],
{ "parserOptions": {
"files": [ "project": ["tsconfig.json"],
"*.ts" "createDefaultProgram": true
], },
"parserOptions": { "extends": [
"project": [ "plugin:@angular-eslint/recommended",
"tsconfig.json" "plugin:@angular-eslint/template/process-inline-templates"
], ],
"createDefaultProgram": true "rules": {
}, "indent": ["error", 4],
"extends": [ "@angular-eslint/directive-selector": [
"plugin:@angular-eslint/recommended", "error",
"plugin:@angular-eslint/template/process-inline-templates" {
], "type": "attribute",
"rules": { "prefix": "app",
"indent": [ "style": "camelCase"
"error", }
4 ],
], "@angular-eslint/component-selector": [
"@angular-eslint/directive-selector": [ "error",
"error", {
{ "type": "element",
"type": "attribute", "prefix": "app",
"prefix": "app", "style": "kebab-case"
"style": "camelCase" }
} ]
], }
"@angular-eslint/component-selector": [ },
"error", {
{ "files": ["*.html"],
"type": "element", "extends": ["plugin:@angular-eslint/template/recommended"],
"prefix": "app", "rules": {}
"style": "kebab-case" }
} ]
]
}
},
{
"files": [
"*.html"
],
"extends": [
"plugin:@angular-eslint/template/recommended"
],
"rules": {}
}
]
} }

View File

@@ -1,46 +1,42 @@
name: ci name: ci
on: on:
push: push:
branches: branches:
- 'main' - "main"
jobs: jobs:
build:
runs-on: ubuntu-latest
build: strategy:
matrix:
node-version: [18.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
runs-on: ubuntu-latest steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: "npm"
- run: npm install
- run: npm run build --if-present
strategy: docker:
matrix: needs: [build]
node-version: [18.x] runs-on: ubuntu-latest
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps: steps:
- uses: actions/checkout@v3 - name: Login to DockerHub
- name: Use Node.js ${{ matrix.node-version }} uses: docker/login-action@v1
uses: actions/setup-node@v3 with:
with: username: ${{ secrets.DOCKERHUB_USERNAME }}
node-version: ${{ matrix.node-version }} password: ${{ secrets.DOCKERHUB_TOKEN }}
cache: 'npm'
- run: npm install
- run: npm run build --if-present
docker: - name: Build and push
uses: docker/build-push-action@v2
needs: [build] with:
runs-on: ubuntu-latest push: true
tags: yoshiunfriendly/frontend-hideyoshi.com:latest
steps:
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v2
with:
push: true
tags: yoshiunfriendly/frontend-hideyoshi.com:latest

View File

@@ -18,9 +18,7 @@
"main": "src/main.ts", "main": "src/main.ts",
"polyfills": "src/polyfills.ts", "polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json", "tsConfig": "tsconfig.app.json",
"allowedCommonJsDependencies": [ "allowedCommonJsDependencies": ["ts-interface-checker"],
"ts-interface-checker"
],
"assets": [ "assets": [
"src/assets", "src/assets",
"src/manifest.webmanifest", "src/manifest.webmanifest",
@@ -104,28 +102,21 @@
"src/manifest.webmanifest", "src/manifest.webmanifest",
"src/manifest.webmanifest" "src/manifest.webmanifest"
], ],
"styles": [ "styles": ["src/styles.css"],
"src/styles.css"
],
"scripts": [] "scripts": []
} }
}, },
"lint": { "lint": {
"builder": "@angular-eslint/builder:lint", "builder": "@angular-eslint/builder:lint",
"options": { "options": {
"lintFilePatterns": [ "lintFilePatterns": ["src/**/*.ts", "src/**/*.html"]
"src/**/*.ts",
"src/**/*.html"
]
} }
} }
} }
} }
}, },
"cli": { "cli": {
"schematicCollections": [ "schematicCollections": ["@angular-eslint/schematics"],
"@angular-eslint/schematics"
],
"analytics": false "analytics": false
}, },
"schematics": { "schematics": {

View File

@@ -2,43 +2,43 @@
// https://karma-runner.github.io/1.0/config/configuration-file.html // https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) { module.exports = function (config) {
config.set({ config.set({
basePath: '', basePath: "",
frameworks: ['jasmine', '@angular-devkit/build-angular'], frameworks: ["jasmine", "@angular-devkit/build-angular"],
plugins: [ plugins: [
require('karma-jasmine'), require("karma-jasmine"),
require('karma-chrome-launcher'), require("karma-chrome-launcher"),
require('karma-jasmine-html-reporter'), require("karma-jasmine-html-reporter"),
require('karma-coverage'), require("karma-coverage"),
require('@angular-devkit/build-angular/plugins/karma') require("@angular-devkit/build-angular/plugins/karma"),
], ],
client: { client: {
jasmine: { jasmine: {
// you can add configuration options for Jasmine here // you can add configuration options for Jasmine here
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
// for example, you can disable the random execution with `random: false` // for example, you can disable the random execution with `random: false`
// or set a specific seed with `seed: 4321` // or set a specific seed with `seed: 4321`
}, },
clearContext: false // leave Jasmine Spec Runner output visible in browser clearContext: false, // leave Jasmine Spec Runner output visible in browser
}, },
jasmineHtmlReporter: { jasmineHtmlReporter: {
suppressAll: true // removes the duplicated traces suppressAll: true, // removes the duplicated traces
}, },
coverageReporter: { coverageReporter: {
dir: require('path').join(__dirname, './coverage/frontend-hideyoshi.com'), dir: require("path").join(
subdir: '.', __dirname,
reporters: [ "./coverage/frontend-hideyoshi.com",
{ type: 'html' }, ),
{ type: 'text-summary' } subdir: ".",
] reporters: [{ type: "html" }, { type: "text-summary" }],
}, },
reporters: ['progress', 'kjhtml'], reporters: ["progress", "kjhtml"],
port: 9876, port: 9876,
colors: true, colors: true,
logLevel: config.LOG_INFO, logLevel: config.LOG_INFO,
autoWatch: true, autoWatch: true,
browsers: ['Chrome'], browsers: ["Chrome"],
singleRun: false, singleRun: false,
restartOnFileChange: true restartOnFileChange: true,
}); });
}; };

View File

@@ -1,30 +1,30 @@
{ {
"$schema": "./node_modules/@angular/service-worker/config/schema.json", "$schema": "./node_modules/@angular/service-worker/config/schema.json",
"index": "/index.html", "index": "/index.html",
"assetGroups": [ "assetGroups": [
{ {
"name": "app", "name": "app",
"installMode": "prefetch", "installMode": "prefetch",
"resources": { "resources": {
"files": [ "files": [
"/favicon.ico", "/favicon.ico",
"/index.html", "/index.html",
"/manifest.webmanifest", "/manifest.webmanifest",
"/*.css", "/*.css",
"/*.js" "/*.js"
] ]
} }
}, },
{ {
"name": "assets", "name": "assets",
"installMode": "lazy", "installMode": "lazy",
"updateMode": "prefetch", "updateMode": "prefetch",
"resources": { "resources": {
"files": [ "files": [
"/assets/**", "/assets/**",
"/*.(svg|cur|jpg|jpeg|png|apng|webp|avif|gif|otf|ttf|woff|woff2)" "/*.(svg|cur|jpg|jpeg|png|apng|webp|avif|gif|otf|ttf|woff|woff2)"
] ]
} }
} }
] ]
} }

31885
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,70 +1,74 @@
{ {
"name": "frontend-hideyoshi.com", "name": "frontend-hideyoshi.com",
"version": "0.0.0", "version": "0.0.0",
"scripts": { "scripts": {
"start": "node ./server.js", "start": "node ./server.js",
"build": "ng build", "build": "ng build",
"serve": "ng serve", "serve": "ng serve",
"serve:prod": "ng serve --configuration=production", "serve:prod": "ng serve --configuration=production",
"build:prod": "ng build --configuration=production" "build:prod": "ng build --configuration=production"
}, },
"proxy": { "proxy": {
"/callback": { "/callback": {
"target": "http://localhost:8070" "target": "http://localhost:8070"
}
},
"private": true,
"dependencies": {
"@angular/animations": "^16.2.2",
"@angular/cdk": "^16.2.1",
"@angular/common": "^16.2.2",
"@angular/compiler": "^16.2.2",
"@angular/core": "^16.2.2",
"@angular/forms": "^16.2.2",
"@angular/material": "^16.2.1",
"@angular/platform-browser": "^16.2.2",
"@angular/platform-browser-dynamic": "^16.2.2",
"@angular/router": "^16.2.2",
"@angular/service-worker": "^16.2.2",
"@fortawesome/angular-fontawesome": "^0.13.0",
"@fortawesome/fontawesome-svg-core": "^6.1.1",
"@fortawesome/free-brands-svg-icons": "^6.1.1",
"@fortawesome/free-regular-svg-icons": "^6.1.1",
"@fortawesome/free-solid-svg-icons": "^6.1.1",
"bootstrap": "^4.6.2",
"cookieconsent": "^3.1.1",
"cors": "^2.8.5",
"express": "^4.18.1",
"jquery": "^3.6.0",
"ngx-cookie-service": "^16.0.1",
"ngx-cookieconsent": "^4.0.2",
"normalize.css": "^8.0.1",
"rxjs": "~7.5.0",
"ts-interface-checker": "^1.0.2",
"tslib": "^2.3.0",
"zone.js": "~0.13.1"
},
"devDependencies": {
"@angular-devkit/build-angular": "^16.2.0",
"@angular-eslint/builder": "^16.1.1",
"@angular-eslint/eslint-plugin": "16.1.1",
"@angular-eslint/eslint-plugin-template": "16.1.1",
"@angular-eslint/schematics": "16.1.1",
"@angular-eslint/template-parser": "16.1.1",
"@angular/cli": "^16.2.0",
"@angular/compiler-cli": "^16.2.2",
"@types/jasmine": "~4.0.0",
"@types/node": "^18.11.19",
"@typescript-eslint/eslint-plugin": "^5.59.2",
"@typescript-eslint/parser": "^5.59.2",
"eslint": "^8.39.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.1",
"jasmine-core": "~4.1.0",
"karma": "~6.3.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.0.0",
"karma-jasmine-html-reporter": "~1.7.0",
"prettier": "^3.0.3",
"prettier-eslint": "^16.1.1",
"ts-interface-builder": "^0.3.3",
"typescript": "~4.9.5"
} }
},
"private": true,
"dependencies": {
"@angular/animations": "^16.2.2",
"@angular/cdk": "^16.2.1",
"@angular/common": "^16.2.2",
"@angular/compiler": "^16.2.2",
"@angular/core": "^16.2.2",
"@angular/forms": "^16.2.2",
"@angular/material": "^16.2.1",
"@angular/platform-browser": "^16.2.2",
"@angular/platform-browser-dynamic": "^16.2.2",
"@angular/router": "^16.2.2",
"@angular/service-worker": "^16.2.2",
"@fortawesome/angular-fontawesome": "^0.13.0",
"@fortawesome/fontawesome-svg-core": "^6.1.1",
"@fortawesome/free-brands-svg-icons": "^6.1.1",
"@fortawesome/free-regular-svg-icons": "^6.1.1",
"@fortawesome/free-solid-svg-icons": "^6.1.1",
"bootstrap": "^4.6.2",
"cookieconsent": "^3.1.1",
"cors": "^2.8.5",
"express": "^4.18.1",
"jquery": "^3.6.0",
"ngx-cookie-service": "^16.0.1",
"ngx-cookieconsent": "^4.0.2",
"normalize.css": "^8.0.1",
"rxjs": "~7.5.0",
"ts-interface-checker": "^1.0.2",
"tslib": "^2.3.0",
"zone.js": "~0.13.1"
},
"devDependencies": {
"@angular-devkit/build-angular": "^16.2.0",
"@angular-eslint/builder": "^16.1.1",
"@angular-eslint/eslint-plugin": "16.1.1",
"@angular-eslint/eslint-plugin-template": "16.1.1",
"@angular-eslint/schematics": "16.1.1",
"@angular-eslint/template-parser": "16.1.1",
"@angular/cli": "^16.2.0",
"@angular/compiler-cli": "^16.2.2",
"@types/jasmine": "~4.0.0",
"@types/node": "^18.11.19",
"@typescript-eslint/eslint-plugin": "^5.59.2",
"@typescript-eslint/parser": "^5.59.2",
"eslint": "^8.39.0",
"jasmine-core": "~4.1.0",
"karma": "~6.3.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.0.0",
"karma-jasmine-html-reporter": "~1.7.0",
"ts-interface-builder": "^0.3.3",
"typescript": "~4.9.5"
}
} }

View File

@@ -1,6 +1,6 @@
const express = require('express'); const express = require("express");
const cors = require('cors'); const cors = require("cors");
const path = require('path'); const path = require("path");
const PKG_NAME = "frontend-hideyoshi.com"; const PKG_NAME = "frontend-hideyoshi.com";
@@ -9,7 +9,7 @@ app.use(cors());
app.use(express.static(`${__dirname}/dist/${PKG_NAME}`)); app.use(express.static(`${__dirname}/dist/${PKG_NAME}`));
app.get('/*', (req, res) => { app.get("/*", (req, res) => {
res.sendFile(path.join(`${__dirname}/dist/${PKG_NAME}/index.html`)); res.sendFile(path.join(`${__dirname}/dist/${PKG_NAME}/index.html`));
}); });

View File

@@ -8,26 +8,21 @@ const routes: Routes = [
{ {
path: '', path: '',
redirectTo: '/home', redirectTo: '/home',
pathMatch: 'full' pathMatch: 'full',
}, },
{ {
path: 'home', path: 'home',
component: HomeComponent component: HomeComponent,
}, },
{ {
path: 'callback', path: 'callback',
component: CallbackComponent component: CallbackComponent,
} },
] ];
@NgModule({ @NgModule({
declarations: [], declarations: [],
imports: [ imports: [CommonModule, RouterModule.forRoot(routes)],
CommonModule, exports: [RouterModule],
RouterModule.forRoot(routes)
],
exports: [
RouterModule
]
}) })
export class AppRouterModule { } export class AppRouterModule {}

View File

@@ -3,19 +3,15 @@ import { CommonModule } from '@angular/common';
import { environment } from 'src/environments/environment'; import { environment } from 'src/environments/environment';
import { ServiceWorkerModule } from '@angular/service-worker'; import { ServiceWorkerModule } from '@angular/service-worker';
@NgModule({ @NgModule({
declarations: [], declarations: [],
imports: [ imports: [
CommonModule, CommonModule,
ServiceWorkerModule.register('ngsw-worker.js', { ServiceWorkerModule.register('ngsw-worker.js', {
enabled: environment.production, enabled: environment.production,
registrationStrategy: 'registerWhenStable:30000' registrationStrategy: 'registerWhenStable:30000',
}) }),
], ],
exports: [ exports: [ServiceWorkerModule],
ServiceWorkerModule
]
}) })
export class AppServiceWorkerModule { } export class AppServiceWorkerModule {}

View File

@@ -4,9 +4,7 @@ import { AppComponent } from './app.component';
describe('AppComponent', () => { describe('AppComponent', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ declarations: [AppComponent],
AppComponent
],
}).compileComponents(); }).compileComponents();
}); });
@@ -26,6 +24,8 @@ describe('AppComponent', () => {
const fixture = TestBed.createComponent(AppComponent); const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges(); fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement; const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('.content span')?.textContent).toContain('frontend-hideyoshi.com app is running!'); expect(compiled.querySelector('.content span')?.textContent).toContain(
'frontend-hideyoshi.com app is running!',
);
}); });
}); });

View File

@@ -1,22 +1,22 @@
import {Component, OnDestroy, OnInit} from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core';
import { AuthService } from './shared/auth/auth.service'; import { AuthService } from './shared/auth/auth.service';
import {UpdateService} from "./shared/service-worker/update.service"; import { UpdateService } from './shared/service-worker/update.service';
import { import {
NgcCookieConsentService, NgcCookieConsentService,
NgcInitializationErrorEvent, NgcInitializationErrorEvent,
NgcInitializingEvent, NgcInitializingEvent,
NgcNoCookieLawEvent, NgcStatusChangeEvent NgcNoCookieLawEvent,
} from "ngx-cookieconsent"; NgcStatusChangeEvent,
import {Subscription} from "rxjs"; } from 'ngx-cookieconsent';
import {CookieConsertService} from "./shared/cookie-consent/cookie-consert.service"; import { Subscription } from 'rxjs';
import { CookieConsertService } from './shared/cookie-consent/cookie-consert.service';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrls: ['./app.component.css'] styleUrls: ['./app.component.css'],
}) })
export class AppComponent implements OnInit { export class AppComponent implements OnInit {
title = 'frontend-hideyoshi.com'; title = 'frontend-hideyoshi.com';
cookieStatusChangeSubscription!: Subscription; cookieStatusChangeSubscription!: Subscription;
@@ -25,28 +25,30 @@ export class AppComponent implements OnInit {
private authService: AuthService, private authService: AuthService,
private ccService: NgcCookieConsentService, private ccService: NgcCookieConsentService,
private cookieConsentService: CookieConsertService, private cookieConsentService: CookieConsertService,
private serviceWorker: UpdateService) { private serviceWorker: UpdateService,
) {
this.serviceWorker.checkForUpdates(); this.serviceWorker.checkForUpdates();
} }
ngOnInit(): void { ngOnInit(): void {
this.authService.autoLogin(); this.authService.autoLogin();
let cookieConsentStatus = this.cookieConsentService.getCookieConsentStatusFromLocalStorage(); let cookieConsentStatus =
this.cookieConsentService.getCookieConsentStatusFromLocalStorage();
if (cookieConsentStatus) { if (cookieConsentStatus) {
this.ccService.destroy(); this.ccService.destroy();
} }
this.cookieStatusChangeSubscription = this.ccService.statusChange$.subscribe( this.cookieStatusChangeSubscription =
(event: NgcStatusChangeEvent) => { this.ccService.statusChange$.subscribe(
if (event.status === 'allow') { (event: NgcStatusChangeEvent) => {
this.cookieConsentService.consent(); if (event.status === 'allow') {
} else if (event.status === 'deny') { this.cookieConsentService.consent();
this.cookieConsentService.decline(); } else if (event.status === 'deny') {
} this.cookieConsentService.decline();
} }
); },
);
} }
} }

View File

@@ -10,14 +10,10 @@ import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { AppServiceWorkerModule } from './app-service-worker.module'; import { AppServiceWorkerModule } from './app-service-worker.module';
import { ServiceWorkerModule } from '@angular/service-worker'; import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment'; import { environment } from '../environments/environment';
import {FooterComponent} from "./footer/footer.component"; import { FooterComponent } from './footer/footer.component';
@NgModule({ @NgModule({
declarations: [ declarations: [AppComponent, HomeComponent, FooterComponent],
AppComponent,
HomeComponent,
FooterComponent
],
imports: [ imports: [
BrowserModule, BrowserModule,
HeaderModule, HeaderModule,
@@ -27,10 +23,10 @@ import {FooterComponent} from "./footer/footer.component";
FontAwesomeModule, FontAwesomeModule,
ServiceWorkerModule.register('ngsw-worker.js', { ServiceWorkerModule.register('ngsw-worker.js', {
enabled: environment.production, enabled: environment.production,
registrationStrategy: 'registerWhenStable:30000' registrationStrategy: 'registerWhenStable:30000',
}) }),
], ],
providers: [], providers: [],
bootstrap: [AppComponent] bootstrap: [AppComponent],
}) })
export class AppModule { } export class AppModule {}

View File

@@ -5,23 +5,27 @@
<section class="mb-2"> <section class="mb-2">
<!-- Twitter --> <!-- Twitter -->
<a class="btn footer-btn" href="https://twitter.com/NakazoneVitor"> <a class="btn footer-btn" href="https://twitter.com/NakazoneVitor">
<fa-icon class="input-div-icon" <fa-icon class="input-div-icon" [icon]="_twitterIcon">
[icon]="_twitterIcon">
</fa-icon> </fa-icon>
</a> </a>
<!-- Linkedin --> <!-- Linkedin -->
<a class="btn footer-btn" href="https://www.linkedin.com/in/vitor-hideyoshi/" role="button"> <a
<fa-icon class="input-div-icon" class="btn footer-btn"
[icon]="_linkedinIcon"> href="https://www.linkedin.com/in/vitor-hideyoshi/"
role="button"
>
<fa-icon class="input-div-icon" [icon]="_linkedinIcon">
</fa-icon> </fa-icon>
</a> </a>
<!-- Github --> <!-- Github -->
<a class="btn footer-btn" href="https://github.com/HideyoshiNakazone" role="button"> <a
<fa-icon class="input-div-icon" class="btn footer-btn"
[icon]="_githubIcon"> href="https://github.com/HideyoshiNakazone"
</fa-icon> role="button"
>
<fa-icon class="input-div-icon" [icon]="_githubIcon"> </fa-icon>
</a> </a>
</section> </section>
<!-- Section: Social media --> <!-- Section: Social media -->
@@ -29,9 +33,11 @@
<!-- Grid container --> <!-- Grid container -->
<!-- Copyright --> <!-- Copyright -->
<div class="text-center p-3" style="background-color: #2E2E2E;"> <div class="text-center p-3" style="background-color: #2e2e2e">
© 2023 Copyright: © 2023 Copyright:
<a class="text-white" href="https://hideyoshi.com.br/">Hideyoshi Solutions</a> <a class="text-white" href="https://hideyoshi.com.br/"
>Hideyoshi Solutions</a
>
</div> </div>
<!-- Copyright --> <!-- Copyright -->
</footer> </footer>

View File

@@ -3,19 +3,19 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FooterComponent } from './footer.component'; import { FooterComponent } from './footer.component';
describe('FooterComponent', () => { describe('FooterComponent', () => {
let component: FooterComponent; let component: FooterComponent;
let fixture: ComponentFixture<FooterComponent>; let fixture: ComponentFixture<FooterComponent>;
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [FooterComponent] declarations: [FooterComponent],
});
fixture = TestBed.createComponent(FooterComponent);
component = fixture.componentInstance;
fixture.detectChanges();
}); });
fixture = TestBed.createComponent(FooterComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy();
}); });
}); });

View File

@@ -1,11 +1,14 @@
import {Component} from '@angular/core'; import { Component } from '@angular/core';
import {faGithub, faLinkedinIn, faTwitter} from "@fortawesome/free-brands-svg-icons"; import {
faGithub,
faLinkedinIn,
faTwitter,
} from '@fortawesome/free-brands-svg-icons';
@Component({ @Component({
selector: 'app-footer', selector: 'app-footer',
templateUrl: './footer.component.html', templateUrl: './footer.component.html',
styleUrls: ['./footer.component.css'] styleUrls: ['./footer.component.css'],
}) })
export class FooterComponent { export class FooterComponent {
_githubIcon = faGithub; _githubIcon = faGithub;

View File

@@ -2,19 +2,19 @@
width: fit-content; width: fit-content;
border-radius: 8px; border-radius: 8px;
background-color: #ffffff; background-color: #ffffff;
border: 1px solid rgba(46, 46, 46, .3); border: 1px solid rgba(46, 46, 46, 0.3);
box-sizing: 0 5px 25px rgba(0, 0, 0, 0.1); box-sizing: 0 5px 25px rgba(0, 0, 0, 0.1);
box-shadow: 1px 1px 4px 1px rgba(0, 0, 0, 0.2); box-shadow: 1px 1px 4px 1px rgba(0, 0, 0, 0.2);
} }
.dropdown:before { .dropdown:before {
content: ''; content: "";
width: 15px; width: 15px;
height: 15px; height: 15px;
position: absolute; position: absolute;
background-color: #ffffff; background-color: #ffffff;
border-top: 1px solid rgba(46, 46, 46, .3); border-top: 1px solid rgba(46, 46, 46, 0.3);
border-left: 1px solid rgba(46, 46, 46, .3); border-left: 1px solid rgba(46, 46, 46, 0.3);
transform: translateX(120px) translateY(-50%) rotate(45deg); transform: translateX(120px) translateY(-50%) rotate(45deg);
} }
.info { .info {
@@ -59,12 +59,12 @@
.dropdown-item:hover .icon-box fa-icon { .dropdown-item:hover .icon-box fa-icon {
opacity: 1; opacity: 1;
color: #f44336; color: #f44336;
transition: .5s; transition: 0.5s;
} }
.dropdown-item p { .dropdown-item p {
color: #555555; color: #555555;
font-family: 'Montserrat', sans-serif; font-family: "Montserrat", sans-serif;
font-weight: 400; font-weight: 400;
text-decoration: none; text-decoration: none;
padding-left: 10px; padding-left: 10px;

View File

@@ -6,9 +6,12 @@
[ignoreElementList]="ignoreClickOutside" [ignoreElementList]="ignoreClickOutside"
[@dropdownState]="dropDownState" [@dropdownState]="dropDownState"
(@dropdownState.start)="$event.element.style.display = 'block'" (@dropdownState.start)="$event.element.style.display = 'block'"
(@dropdownState.done)="$event.element.style.display = (state ? 'block' : 'none')"> (@dropdownState.done)="
$event.element.style.display = state ? 'block' : 'none'
"
>
<div class="info"> <div class="info">
<h3>{{ this.user ? this.user.username : 'User Account' }}</h3> <h3>{{ this.user ? this.user.username : "User Account" }}</h3>
</div> </div>
<div #management> <div #management>
<ul class="user-management" *ngIf="!this.user"> <ul class="user-management" *ngIf="!this.user">
@@ -34,13 +37,19 @@
</li> </li>
<li class="dropdown-item" (click)="onHelpClicked()"> <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>
<p>Help</p> <p>Help</p>
</li> </li>
<li class="dropdown-item" (click)="onLogout()"> <li class="dropdown-item" (click)="onLogout()">
<div class="icon-box"> <div class="icon-box">
<fa-icon class="fas fa-sign-out-alt" [icon]="signOutAltIcon"></fa-icon> <fa-icon
class="fas fa-sign-out-alt"
[icon]="signOutAltIcon"
></fa-icon>
</div> </div>
<p>Logout</p> <p>Logout</p>
</li> </li>

View File

@@ -8,9 +8,8 @@ describe('HeaderDropdownComponent', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ HeaderDropdownComponent ] declarations: [HeaderDropdownComponent],
}) }).compileComponents();
.compileComponents();
fixture = TestBed.createComponent(HeaderDropdownComponent); fixture = TestBed.createComponent(HeaderDropdownComponent);
component = fixture.componentInstance; component = fixture.componentInstance;

View File

@@ -1,12 +1,32 @@
import { animate, state, style, transition, trigger } from '@angular/animations'; import {
import {Component, ComponentRef, EventEmitter, Input, OnDestroy, OnInit, Output, ViewContainerRef} from '@angular/core'; animate,
import { faEdit, faQuestionCircle, faSignOutAlt, faUser } from '@fortawesome/free-solid-svg-icons'; state,
style,
transition,
trigger,
} from '@angular/animations';
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 { 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 { 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';
@Component({ @Component({
selector: 'app-header-dropdown', selector: 'app-header-dropdown',
@@ -14,19 +34,24 @@ import {MyProfileComponent} from "../header-popup/my-profile/my-profile.componen
styleUrls: ['./header-dropdown.component.css'], styleUrls: ['./header-dropdown.component.css'],
animations: [ animations: [
trigger('dropdownState', [ trigger('dropdownState', [
state('hide', style({ state(
'opacity': '0' 'hide',
})), style({
state('show', style({ opacity: '0',
'opacity': '1' }),
})), ),
state(
'show',
style({
opacity: '1',
}),
),
transition('hide => show', animate('20ms ease-in')), transition('hide => show', animate('20ms ease-in')),
transition('show => hide', animate('5ms ease-out')) transition('show => hide', animate('5ms ease-out')),
]) ]),
] ],
}) })
export class HeaderDropdownComponent implements OnInit, OnDestroy { export class HeaderDropdownComponent implements OnInit, OnDestroy {
userIcon = faUser; userIcon = faUser;
editIcon = faEdit; editIcon = faEdit;
@@ -40,38 +65,41 @@ export class HeaderDropdownComponent implements OnInit, OnDestroy {
private userSubscription!: Subscription; private userSubscription!: Subscription;
@Input() @Input()
state: boolean = false; state: boolean = false;
@Input() @Input()
ignoreClickOutside!: HTMLDivElement[]; ignoreClickOutside!: HTMLDivElement[];
@Output() @Output()
clickOutside = new EventEmitter(); clickOutside = new EventEmitter();
@Output() @Output()
loginPopupState: EventEmitter<boolean> = new EventEmitter(); loginPopupState: EventEmitter<boolean> = new EventEmitter();
@Output() @Output()
signupPopupState: EventEmitter<boolean> = new EventEmitter(); signupPopupState: EventEmitter<boolean> = new EventEmitter();
@Output() @Output()
helpPopupState: EventEmitter<boolean> = new EventEmitter(); helpPopupState: EventEmitter<boolean> = new EventEmitter();
@Output() @Output()
myProfilePopupState: EventEmitter<boolean> = new EventEmitter(); myProfilePopupState: EventEmitter<boolean> = new EventEmitter();
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) => {
if (res && UserChecker.test(res)) { if (res && UserChecker.test(res)) {
this.user = <User>res; this.user = <User>res;
} else { } else {
this.user = null; this.user = null;
} }
} },
) );
} }
ngOnDestroy(): void { ngOnDestroy(): void {

View File

@@ -3,21 +3,20 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CallbackComponent } from './callback.component'; import { CallbackComponent } from './callback.component';
describe('CallbackComponent', () => { describe('CallbackComponent', () => {
let component: CallbackComponent; let component: CallbackComponent;
let fixture: ComponentFixture<CallbackComponent>; let fixture: ComponentFixture<CallbackComponent>;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ CallbackComponent ] declarations: [CallbackComponent],
}) }).compileComponents();
.compileComponents();
fixture = TestBed.createComponent(CallbackComponent); fixture = TestBed.createComponent(CallbackComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy();
}); });
}); });

View File

@@ -5,35 +5,32 @@ import { AuthService } from 'src/app/shared/auth/auth.service';
@Component({ @Component({
selector: 'app-callback', selector: 'app-callback',
templateUrl: './callback.component.html', templateUrl: './callback.component.html',
styleUrls: ['./callback.component.css'] styleUrls: ['./callback.component.css'],
}) })
export class CallbackComponent implements OnInit { export class CallbackComponent implements OnInit {
constructor(
constructor(private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router, private router: Router,
private authService: AuthService) { } private authService: AuthService,
) {}
ngOnInit(): void { ngOnInit(): void {
this.route.queryParams.subscribe((p) => {
this.route.queryParams.subscribe(p => {
let auth: 'google' | 'github' = p['auth']; let auth: 'google' | 'github' = p['auth'];
switch (auth) { switch (auth) {
case "github": case 'github':
this.authService.loginGithubUser(p) this.authService.loginGithubUser(p);
break; break;
case "google": case 'google':
this.authService.loginGoogleUser(p) this.authService.loginGoogleUser(p);
break; break;
default: default:
console.log(`Unimplemented auth: ${auth}`) console.log(`Unimplemented auth: ${auth}`);
break; break;
} }
this.router.navigate(['/home']) this.router.navigate(['/home']);
});
})
} }
} }

View File

@@ -1,3 +1,3 @@
<div class="error-box" *ngIf="errorMessage"> <div class="error-box" *ngIf="errorMessage">
{{errorMessage}} {{ errorMessage }}
</div> </div>

View File

@@ -3,21 +3,20 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ErrorBoxComponent } from './error-box.component'; import { ErrorBoxComponent } from './error-box.component';
describe('ErrorBoxComponent', () => { describe('ErrorBoxComponent', () => {
let component: ErrorBoxComponent; let component: ErrorBoxComponent;
let fixture: ComponentFixture<ErrorBoxComponent>; let fixture: ComponentFixture<ErrorBoxComponent>;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ ErrorBoxComponent ] declarations: [ErrorBoxComponent],
}) }).compileComponents();
.compileComponents();
fixture = TestBed.createComponent(ErrorBoxComponent); fixture = TestBed.createComponent(ErrorBoxComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy();
}); });
}); });

View File

@@ -1,14 +1,13 @@
import {Component, Input} from '@angular/core'; import { Component, Input } from '@angular/core';
@Component({ @Component({
selector: 'app-error-box', selector: 'app-error-box',
templateUrl: './error-box.component.html', templateUrl: './error-box.component.html',
styleUrls: ['./error-box.component.css'] styleUrls: ['./error-box.component.css'],
}) })
export class ErrorBoxComponent { export class ErrorBoxComponent {
@Input() @Input()
errorMessage: string|null = "Error, please try again later." errorMessage: string | null = 'Error, please try again later.';
constructor() { }
constructor() {}
} }

View File

@@ -1,28 +1,32 @@
<app-popup [state]="state" <app-popup
(stateChange)="onStateChange($event)" [state]="state"
[ignoreClickOutside]="ignoreClickOutside"> (stateChange)="onStateChange($event)"
[ignoreClickOutside]="ignoreClickOutside"
>
<div class="help-container container m-0 overflow-hidden"> <div class="help-container container m-0 overflow-hidden">
<p> <p>
This is a simple example project to demonstrate This is a simple example project to demonstrate User Authentication
User Authentication and Authorization using and Authorization using
<a href="https://spring.io/projects/spring-security" target="_blank">Spring Security</a> <a href="https://spring.io/projects/spring-security" target="_blank"
>Spring Security</a
>
and and
<a href="https://docs.spring.io/spring-security/reference/servlet/oauth2/" target="_blank">OAuth2</a>. <a
href="https://docs.spring.io/spring-security/reference/servlet/oauth2/"
target="_blank"
>OAuth2</a
>.
<br/><br/> <br /><br />
The only data stored is your email address, username and name. The only data stored is your email address, username and name. This
This data is stored in a database and is used to authenticate you data is stored in a database and is used to authenticate you and
and will not be used for any other purpose. will not be used for any other purpose.
<br/><br/> <br /><br />
All data can be deleted by clicking the "Delete Account" button All data can be deleted by clicking the "Delete Account" button on
on the "My Profile" option. the "My Profile" option.
</p> </p>
</div> </div>
</app-popup> </app-popup>

View File

@@ -3,21 +3,20 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HelpComponent } from './help.component'; import { HelpComponent } from './help.component';
describe('HelpComponent', () => { describe('HelpComponent', () => {
let component: HelpComponent; let component: HelpComponent;
let fixture: ComponentFixture<HelpComponent>; let fixture: ComponentFixture<HelpComponent>;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ HelpComponent ] declarations: [HelpComponent],
}) }).compileComponents();
.compileComponents();
fixture = TestBed.createComponent(HelpComponent); fixture = TestBed.createComponent(HelpComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy();
}); });
}); });

View File

@@ -1,22 +1,21 @@
import {Component, EventEmitter, Input, Output} from '@angular/core'; import { Component, EventEmitter, Input, Output } from '@angular/core';
@Component({ @Component({
selector: 'app-help', selector: 'app-help',
templateUrl: './help.component.html', templateUrl: './help.component.html',
styleUrls: ['./help.component.css'] styleUrls: ['./help.component.css'],
}) })
export class HelpComponent { export class HelpComponent {
@Input()
state: boolean = false;
@Input() @Input()
state: boolean = false; ignoreClickOutside!: HTMLDivElement[];
@Input()
ignoreClickOutside!: HTMLDivElement[];
@Output() @Output()
stateChange = new EventEmitter<boolean>(); stateChange = new EventEmitter<boolean>();
constructor() { } constructor() {}
onStateChange(state: boolean) { onStateChange(state: boolean) {
this.stateChange.emit(state); this.stateChange.emit(state);

View File

@@ -17,7 +17,7 @@
} }
.authentication-body .btn { .authentication-body .btn {
background-color: #D8291C !important; background-color: #d8291c !important;
text-decoration: none; text-decoration: none;
border-radius: 8px; border-radius: 8px;
color: #ffffff; color: #ffffff;
@@ -67,7 +67,7 @@
border-bottom: 2px solid #7676769b; border-bottom: 2px solid #7676769b;
} }
.input-div:after { .input-div:after {
content: ''; content: "";
left: 0; left: 0;
right: 0; right: 0;
width: 0; width: 0;
@@ -81,11 +81,12 @@
.input-div:hover:after, .input-div:hover:after,
.input-div:has(input.form-control:focus):after { .input-div:has(input.form-control:focus):after {
width: 100%; width: 100%;
transition: .4s; transition: 0.4s;
overflow: hidden; overflow: hidden;
} }
.input-div > .form-control, .input-div > .form-control:hover{ .input-div > .form-control,
.input-div > .form-control:hover {
border: none; border: none;
border-color: inherit; border-color: inherit;
-webkit-box-shadow: none; -webkit-box-shadow: none;
@@ -98,7 +99,7 @@
font-size: 17px; font-size: 17px;
color: #767676; color: #767676;
} }
.input-div >.form-control:-webkit-autofill { .input-div > .form-control:-webkit-autofill {
-webkit-text-fill-color: #767676; -webkit-text-fill-color: #767676;
box-shadow: 0 0 0px 1000px #ffffff inset; box-shadow: 0 0 0px 1000px #ffffff inset;
-webkit-box-shadow: 0 0 0px 1000px #ffffff inset; -webkit-box-shadow: 0 0 0px 1000px #ffffff inset;
@@ -108,19 +109,16 @@
.input-div:hover > .form-control::placeholder, .input-div:hover > .form-control::placeholder,
.input-div:hover > .input-div-icon, .input-div:hover > .input-div-icon,
.input-div:has(input.form-control:focus) > .input-div-icon { .input-div:has(input.form-control:focus) > .input-div-icon {
color: #D8291C; color: #d8291c;
transition: .3s; transition: 0.3s;
} }
.input-div:hover > .form-control::placeholder, .input-div:hover > .form-control::placeholder,
.input-div:has(input.form-control:focus) > .form-control::placeholder { .input-div:has(input.form-control:focus) > .form-control::placeholder {
font-weight: 500; font-weight: 500;
transition: .3s; transition: 0.3s;
} }
@media (min-width: 767px) {
@media (min-width:767px) {
.authentication-container { .authentication-container {
min-width: 630px; min-width: 630px;
} }
@@ -150,5 +148,4 @@
border-right: 2px solid #80808076; border-right: 2px solid #80808076;
height: 100%; height: 100%;
} }
} }

View File

@@ -1,41 +1,56 @@
<app-popup [state]="state" <app-popup
[state]="state"
(stateChange)="onStateChange($event)" (stateChange)="onStateChange($event)"
[ignoreClickOutside]="ignoreClickOutside"> [ignoreClickOutside]="ignoreClickOutside"
>
<div class="container m-0 overflow-hidden" <div
[@resizeContainerForErrorMessage]="hideErrorMessage()"> class="container m-0 overflow-hidden"
[@resizeContainerForErrorMessage]="hideErrorMessage()"
<app-error-box [errorMessage]="errorMessage" >
[@showErrorMessage]="showErrorMessage()"> <app-error-box
[errorMessage]="errorMessage"
[@showErrorMessage]="showErrorMessage()"
>
</app-error-box> </app-error-box>
<div class="container authentication-container" <div
[@hideAuthContainer]="hideErrorMessage()" class="container authentication-container"
(@hideAuthContainer.done)="hideAuthContainer($event)"> [@hideAuthContainer]="hideErrorMessage()"
(@hideAuthContainer.done)="hideAuthContainer($event)"
>
<div class="row"> <div class="row">
<div class="col-lg-6 authentication-body"> <div class="col-lg-6 authentication-body">
<form [formGroup]="loginForm" (ngSubmit)="onLogin()"> <form [formGroup]="loginForm" (ngSubmit)="onLogin()">
<div class="input-div"> <div class="input-div">
<fa-icon class="input-div-icon" <fa-icon class="input-div-icon" [icon]="_userIcon">
[icon]="_userIcon">
</fa-icon> </fa-icon>
<input type="text" id="username" <input
formControlName="username" type="text"
class="form-control" id="username"
placeholder="Username"> formControlName="username"
class="form-control"
placeholder="Username"
/>
</div> </div>
<div class="input-div"> <div class="input-div">
<fa-icon class="input-div-icon" <fa-icon
[icon]="_passwordIcon"> class="input-div-icon"
[icon]="_passwordIcon"
>
</fa-icon> </fa-icon>
<input type="password" id="password" <input
formControlName="password" type="password"
class="form-control" id="password"
placeholder="Password"> formControlName="password"
class="form-control"
placeholder="Password"
/>
</div> </div>
<button class="btn" <button
[disabled]="loginForm.invalid" class="btn"
type="submit"> [disabled]="loginForm.invalid"
type="submit"
>
Login Login
</button> </button>
</form> </form>
@@ -44,30 +59,38 @@
<div class="line"></div> <div class="line"></div>
</div> </div>
<div class="col-lg-6 authentication-body"> <div class="col-lg-6 authentication-body">
<button mat-button <button
class="oauth-button d-flex justify-content-center align-items-center" mat-button
[disabled]="isCookieBlocked" class="oauth-button d-flex justify-content-center align-items-center"
(click)="onGoogleLogin()"> [disabled]="isCookieBlocked"
<mat-icon *ngIf="!isCookieBlocked" (click)="onGoogleLogin()"
style="width: 50px; height:30px" >
svgIcon="google-logo"></mat-icon> <mat-icon
<mat-icon *ngIf="isCookieBlocked" *ngIf="!isCookieBlocked"
style="width: 50px; height:30px" style="width: 50px; height: 30px"
svgIcon="google-disabled-logo"></mat-icon> svgIcon="google-logo"
></mat-icon>
<mat-icon
*ngIf="isCookieBlocked"
style="width: 50px; height: 30px"
svgIcon="google-disabled-logo"
></mat-icon>
Login With Google Login With Google
</button> </button>
<button mat-button <button
class="oauth-button d-flex justify-content-center align-items-center" mat-button
[disabled]="isCookieBlocked" class="oauth-button d-flex justify-content-center align-items-center"
(click)="onGithubLogin()"> [disabled]="isCookieBlocked"
<mat-icon style="width: 50px; height:30px" (click)="onGithubLogin()"
svgIcon="github-logo"></mat-icon> >
<mat-icon
style="width: 50px; height: 30px"
svgIcon="github-logo"
></mat-icon>
Login With Github Login With Github
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</app-popup> </app-popup>

View File

@@ -8,9 +8,8 @@ describe('LoginComponent', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ LoginComponent ] declarations: [LoginComponent],
}) }).compileComponents();
.compileComponents();
fixture = TestBed.createComponent(LoginComponent); fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance; component = fixture.componentInstance;

View File

@@ -1,24 +1,44 @@
import { AfterViewInit, ChangeDetectorRef, Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; import {
AfterViewInit,
ChangeDetectorRef,
Component,
EventEmitter,
Input,
OnDestroy,
OnInit,
Output,
} from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms'; import { FormControl, FormGroup, Validators } from '@angular/forms';
import { MatIconRegistry } from '@angular/material/icon'; import { MatIconRegistry } from '@angular/material/icon';
import { DomSanitizer } from '@angular/platform-browser'; import { DomSanitizer } from '@angular/platform-browser';
import { faLock, faUser } from '@fortawesome/free-solid-svg-icons'; import { faLock, 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 { HttpError } from 'src/app/shared/model/httpError/httpError.model'; import { HttpError } from 'src/app/shared/model/httpError/httpError.model';
import HttpErrorChecker from 'src/app/shared/model/httpError/httpErrorChecker'; import HttpErrorChecker from 'src/app/shared/model/httpError/httpErrorChecker';
import UserChecker from 'src/app/shared/model/user/user.checker'; import UserChecker from 'src/app/shared/model/user/user.checker';
import { User } from 'src/app/shared/model/user/user.model'; import { User } from 'src/app/shared/model/user/user.model';
import {animate, animateChild, group, query, state, style, transition, trigger} from "@angular/animations"; import {
import {ValidatePasswordValidator} from "../../../shared/validators/validate-password.validator"; animate,
import {ValidateNotEmptyValidator} from "../../../shared/validators/validate-not-empty.validator"; animateChild,
import {NgcCookieConsentService, NgcStatusChangeEvent} from "ngx-cookieconsent"; group,
import {CookieConsertService} from "../../../shared/cookie-consent/cookie-consert.service"; query,
state,
style,
transition,
trigger,
} from '@angular/animations';
import { ValidatePasswordValidator } from '../../../shared/validators/validate-password.validator';
import { ValidateNotEmptyValidator } from '../../../shared/validators/validate-not-empty.validator';
import {
NgcCookieConsentService,
NgcStatusChangeEvent,
} from 'ngx-cookieconsent';
import { CookieConsertService } from '../../../shared/cookie-consent/cookie-consert.service';
const GOOGLE_LOGO_SVG = 'assets/img/providers/google.svg';
const GOOGLE_LOGO_SVG = "assets/img/providers/google.svg"; const GOOGLE_DISABLED_LOGO_SVG = 'assets/img/providers/google-disabled.svg';
const GOOGLE_DISABLED_LOGO_SVG = "assets/img/providers/google-disabled.svg"; const GITHUB_LOGO_SVG = 'assets/img/providers/github.svg';
const GITHUB_LOGO_SVG = "assets/img/providers/github.svg";
@Component({ @Component({
selector: 'app-login', selector: 'app-login',
@@ -26,78 +46,66 @@ const GITHUB_LOGO_SVG = "assets/img/providers/github.svg";
styleUrls: ['./login.component.css'], styleUrls: ['./login.component.css'],
animations: [ animations: [
trigger('resizeContainerForErrorMessage', [ trigger('resizeContainerForErrorMessage', [
state('hide', state(
'hide',
style({ style({
height: '100px', height: '100px',
width: '320px', width: '320px',
}) }),
), ),
transition( transition(
'show => hide', 'show => hide',
group([ group([
query( query('@*', animateChild(), { optional: true }),
"@*", animate('1s ease'),
animateChild(), ]),
{ optional: true } ),
),
animate('1s ease')
])
)
]), ]),
trigger('showErrorMessage', [ trigger('showErrorMessage', [
state('show', state(
'show',
style({ style({
opacity: 1, opacity: 1,
height: '100px', height: '100px',
width: '320px', width: '320px',
}) }),
), ),
state('hide', state(
'hide',
style({ style({
opacity: 0, opacity: 0,
height: '0px', height: '0px',
width: '0px', width: '0px',
}) }),
),
transition(
'* => show',
animate(
'500ms ease-in'
)
), ),
transition('* => show', animate('500ms ease-in')),
]), ]),
trigger('hideAuthContainer', [ trigger('hideAuthContainer', [
state('hide', state(
'hide',
style({ style({
opacity: 0, opacity: 0,
}) }),
), ),
transition( transition(
'show => hide', 'show => hide',
group([ group([
query( query('@*', animateChild(), { optional: true }),
"@*", animate('250ms ease-out'),
animateChild(), ]),
{ optional: true } ),
),
animate(
'250ms ease-out'
)
])
)
]), ]),
] ],
}) })
export class LoginComponent implements OnInit, AfterViewInit, OnDestroy { export class LoginComponent implements OnInit, AfterViewInit, OnDestroy {
@Input()
state: boolean = false;
@Input() @Input()
state: boolean = false; ignoreClickOutside!: HTMLDivElement[];
@Input()
ignoreClickOutside!: HTMLDivElement[];
@Output() @Output()
stateChange = new EventEmitter<boolean>(); stateChange = new EventEmitter<boolean>();
loginForm!: FormGroup; loginForm!: FormGroup;
@@ -121,39 +129,47 @@ export class LoginComponent implements OnInit, AfterViewInit, OnDestroy {
private cookieConsentService: CookieConsertService, private cookieConsentService: CookieConsertService,
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
private matIconRegistry: MatIconRegistry, private matIconRegistry: MatIconRegistry,
private domSanitizer: DomSanitizer) { private domSanitizer: DomSanitizer,
) {
this.matIconRegistry.addSvgIcon( this.matIconRegistry.addSvgIcon(
"google-logo", 'google-logo',
this.domSanitizer.bypassSecurityTrustResourceUrl(GOOGLE_LOGO_SVG) this.domSanitizer.bypassSecurityTrustResourceUrl(GOOGLE_LOGO_SVG),
); );
this.matIconRegistry.addSvgIcon( this.matIconRegistry.addSvgIcon(
"google-disabled-logo", 'google-disabled-logo',
this.domSanitizer.bypassSecurityTrustResourceUrl(GOOGLE_DISABLED_LOGO_SVG) this.domSanitizer.bypassSecurityTrustResourceUrl(
) GOOGLE_DISABLED_LOGO_SVG,
),
);
this.matIconRegistry.addSvgIcon( this.matIconRegistry.addSvgIcon(
"github-logo", 'github-logo',
this.domSanitizer.bypassSecurityTrustResourceUrl(GITHUB_LOGO_SVG) this.domSanitizer.bypassSecurityTrustResourceUrl(GITHUB_LOGO_SVG),
); );
} }
ngOnInit(): void { ngOnInit(): void {
this.loginForm = new FormGroup({ this.loginForm = new FormGroup({
'username': new FormControl(null, [Validators.required, ValidateNotEmptyValidator]), username: new FormControl(null, [
'password': new FormControl(null, [Validators.required, ValidatePasswordValidator]) Validators.required,
ValidateNotEmptyValidator,
]),
password: new FormControl(null, [
Validators.required,
ValidatePasswordValidator,
]),
}); });
this.errorMessage = null; this.errorMessage = null;
this.authSubject = this.authService.authSubject.subscribe( this.authSubject = this.authService.authSubject.subscribe((res) => {
res => { this.validateLogin(res);
this.validateLogin(res); });
}
);
this.cookieStatusChangeSubscription = this.cookieConsentService.cookieStatusChangeSubscription.subscribe( this.cookieStatusChangeSubscription =
(status: boolean) => { this.cookieConsentService.cookieStatusChangeSubscription.subscribe(
this.isCookieBlocked = !status; (status: boolean) => {
console.log("Cookie status: " + status); this.isCookieBlocked = !status;
} console.log('Cookie status: ' + status);
); },
);
if (this.isCookieBlocked) { if (this.isCookieBlocked) {
this.ccService.fadeIn(); this.ccService.fadeIn();
@@ -176,8 +192,8 @@ export class LoginComponent implements OnInit, AfterViewInit, OnDestroy {
onLogin() { onLogin() {
let user: User = { let user: User = {
username: this.loginForm.controls['username'].value, username: this.loginForm.controls['username'].value,
password: this.loginForm.controls['password'].value password: this.loginForm.controls['password'].value,
} };
this.authService.login(user); this.authService.login(user);
} }
@@ -191,11 +207,11 @@ export class LoginComponent implements OnInit, AfterViewInit, OnDestroy {
private validateLogin(res: User | HttpError | null) { private validateLogin(res: User | HttpError | null) {
if (res && UserChecker.test(res)) { if (res && UserChecker.test(res)) {
this.closePopup() this.closePopup();
} if (HttpErrorChecker.test(res)) { }
if (HttpErrorChecker.test(res)) {
this.errorMessage = (<HttpError>res).details; this.errorMessage = (<HttpError>res).details;
} }
} }
private closePopup() { private closePopup() {
@@ -205,23 +221,22 @@ export class LoginComponent implements OnInit, AfterViewInit, OnDestroy {
public showErrorMessage(): string { public showErrorMessage(): string {
if (this.isShowErrorMessage) { if (this.isShowErrorMessage) {
return "show"; return 'show';
} }
return "hide"; return 'hide';
} }
public hideErrorMessage(): string { public hideErrorMessage(): string {
if (!!this.errorMessage) { if (!!this.errorMessage) {
return "hide"; return 'hide';
} }
return "show"; return 'show';
} }
hideAuthContainer(event: any) { hideAuthContainer(event: any) {
if (event.toState === "hide") { if (event.toState === 'hide') {
event.element.style.display = "none"; event.element.style.display = 'none';
this.isShowErrorMessage = true; this.isShowErrorMessage = true;
} }
} }
} }

View File

@@ -36,7 +36,6 @@
} }
.profile-options-container button { .profile-options-container button {
text-decoration: none; text-decoration: none;
border-radius: 8px; border-radius: 8px;
color: #ffffff; color: #ffffff;
@@ -55,9 +54,7 @@
background-color: rgba(216, 41, 28, 0.7) !important; background-color: rgba(216, 41, 28, 0.7) !important;
} }
@media (min-width: 767px) {
@media (min-width:767px) {
.profile-options-container { .profile-options-container {
all: unset; all: unset;
justify-content: space-around; justify-content: space-around;
@@ -96,5 +93,4 @@
border-radius: 50px; border-radius: 50px;
height: 100%; height: 100%;
} }
} }

View File

@@ -1,23 +1,28 @@
<app-popup [state]="state" <app-popup
(stateChange)="onStateChange($event)" [state]="state"
[ignoreClickOutside]="ignoreClickOutside"> (stateChange)="onStateChange($event)"
[ignoreClickOutside]="ignoreClickOutside"
<div class="container m-0 overflow-hidden" >
[@resizeContainerForErrorMessage]="hideErrorMessage()"> <div
class="container m-0 overflow-hidden"
<app-error-box [errorMessage]="errorMessage" [@resizeContainerForErrorMessage]="hideErrorMessage()"
[@showErrorMessage]="showErrorMessage()"> >
<app-error-box
[errorMessage]="errorMessage"
[@showErrorMessage]="showErrorMessage()"
>
</app-error-box> </app-error-box>
<div class="container profile-options-container" <div
[@hideAuthContainer]="hideErrorMessage()" class="container profile-options-container"
(@hideAuthContainer.done)="hideAuthContainer($event)"> [@hideAuthContainer]="hideErrorMessage()"
(@hideAuthContainer.done)="hideAuthContainer($event)"
>
<div class="row profile-options-row"> <div class="row profile-options-row">
<div class="btn-container"> <div class="btn-container">
<app-profile-picture-picker <app-profile-picture-picker
(imageSent)="onProfilePictureSent($event)"> (imageSent)="onProfilePictureSent($event)"
>
</app-profile-picture-picker> </app-profile-picture-picker>
</div> </div>
@@ -26,16 +31,11 @@
</div> </div>
<div class="btn-container"> <div class="btn-container">
<button class="delete-btn" <button class="delete-btn" (click)="onDeleteAccount()">
(click)="onDeleteAccount()">
Delete Account Delete Account
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</app-popup> </app-popup>

View File

@@ -3,21 +3,20 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MyProfileComponent } from './my-profile.component'; import { MyProfileComponent } from './my-profile.component';
describe('MyProfileComponent', () => { describe('MyProfileComponent', () => {
let component: MyProfileComponent; let component: MyProfileComponent;
let fixture: ComponentFixture<MyProfileComponent>; let fixture: ComponentFixture<MyProfileComponent>;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ MyProfileComponent ] declarations: [MyProfileComponent],
}) }).compileComponents();
.compileComponents();
fixture = TestBed.createComponent(MyProfileComponent); fixture = TestBed.createComponent(MyProfileComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy();
}); });
}); });

View File

@@ -1,18 +1,33 @@
import {ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; import {
import {AuthService} from "../../../shared/auth/auth.service"; ChangeDetectorRef,
import {User} from "../../../shared/model/user/user.model"; Component,
import {animate, animateChild, group, query, state, style, transition, trigger} from "@angular/animations"; EventEmitter,
import {MatIconRegistry} from "@angular/material/icon"; Input,
import {DomSanitizer} from "@angular/platform-browser"; OnInit,
import {FormControl, FormGroup, Validators} from "@angular/forms"; Output,
import {ValidateNotEmptyValidator} from "../../../shared/validators/validate-not-empty.validator"; } from '@angular/core';
import {ValidatePasswordValidator} from "../../../shared/validators/validate-password.validator"; import { AuthService } from '../../../shared/auth/auth.service';
import {first, take} from "rxjs"; import { User } from '../../../shared/model/user/user.model';
import UserChecker from "../../../shared/model/user/user.checker"; import {
import HttpErrorChecker from "../../../shared/model/httpError/httpErrorChecker"; animate,
import {HttpError} from "../../../shared/model/httpError/httpError.model"; animateChild,
import {faFileUpload} from "@fortawesome/free-solid-svg-icons"; 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({ @Component({
selector: 'app-my-profile', selector: 'app-my-profile',
@@ -20,81 +35,69 @@ import {faFileUpload} from "@fortawesome/free-solid-svg-icons";
styleUrls: ['./my-profile.component.css'], styleUrls: ['./my-profile.component.css'],
animations: [ animations: [
trigger('resizeContainerForErrorMessage', [ trigger('resizeContainerForErrorMessage', [
state('hide', state(
'hide',
style({ style({
height: '100px', height: '100px',
width: '320px', width: '320px',
}) }),
), ),
transition( transition(
'show => hide', 'show => hide',
group([ group([
query( query('@*', animateChild(), { optional: true }),
"@*", animate('1s ease'),
animateChild(), ]),
{ optional: true } ),
),
animate('1s ease')
])
)
]), ]),
trigger('showErrorMessage', [ trigger('showErrorMessage', [
state('show', state(
'show',
style({ style({
opacity: 1, opacity: 1,
height: '100px', height: '100px',
width: '320px', width: '320px',
}) }),
), ),
state('hide', state(
'hide',
style({ style({
opacity: 0, opacity: 0,
height: '0px', height: '0px',
width: '0px', width: '0px',
}) }),
),
transition(
'* => show',
animate(
'500ms ease-in'
)
), ),
transition('* => show', animate('500ms ease-in')),
]), ]),
trigger('hideAuthContainer', [ trigger('hideAuthContainer', [
state('hide', state(
'hide',
style({ style({
opacity: 0, opacity: 0,
}) }),
), ),
transition( transition(
'show => hide', 'show => hide',
group([ group([
query( query('@*', animateChild(), { optional: true }),
"@*", animate('250ms ease-out'),
animateChild(), ]),
{ optional: true } ),
),
animate(
'250ms ease-out'
)
])
)
]), ]),
] ],
}) })
export class MyProfileComponent implements OnInit { export class MyProfileComponent implements OnInit {
@Input()
state: boolean = false;
@Input() @Input()
state: boolean = false; user!: User | null;
@Input() @Input()
user!: User | null; ignoreClickOutside!: HTMLDivElement[];
@Input()
ignoreClickOutside!: HTMLDivElement[];
@Output() @Output()
stateChange = new EventEmitter<boolean>(); stateChange = new EventEmitter<boolean>();
alterForm!: FormGroup; alterForm!: FormGroup;
@@ -102,15 +105,20 @@ export class MyProfileComponent implements OnInit {
isShowErrorMessage = false; isShowErrorMessage = false;
_fileIcon = faFileUpload _fileIcon = faFileUpload;
constructor(private authService: AuthService) { constructor(private authService: AuthService) {}
}
ngOnInit(): void { ngOnInit(): void {
this.alterForm = new FormGroup({ this.alterForm = new FormGroup({
'username': new FormControl(null, [Validators.required, ValidateNotEmptyValidator]), username: new FormControl(null, [
'password': new FormControl(null, [Validators.required, ValidatePasswordValidator]) Validators.required,
ValidateNotEmptyValidator,
]),
password: new FormControl(null, [
Validators.required,
ValidatePasswordValidator,
]),
}); });
this.errorMessage = null; this.errorMessage = null;
} }
@@ -121,30 +129,30 @@ export class MyProfileComponent implements OnInit {
showErrorMessage(): string { showErrorMessage(): string {
if (this.isShowErrorMessage) { if (this.isShowErrorMessage) {
return "show"; return 'show';
} }
return "hide"; return 'hide';
} }
hideErrorMessage(): string { hideErrorMessage(): string {
if (!!this.errorMessage) { if (!!this.errorMessage) {
return "hide"; return 'hide';
} }
return "show"; return 'show';
} }
onDeleteAccount() { onDeleteAccount() {
this.authService.deleteAccount().subscribe({ this.authService.deleteAccount().subscribe({
next: (response: any) => { next: (response: any) => {
this.authService.logout(); this.authService.logout();
} },
}) });
this.closePopup(); this.closePopup();
} }
hideAuthContainer(event: any) { hideAuthContainer(event: any) {
if (event.toState === "hide") { if (event.toState === 'hide') {
event.element.style.display = "none"; event.element.style.display = 'none';
this.isShowErrorMessage = true; this.isShowErrorMessage = true;
} }
} }
@@ -158,5 +166,4 @@ export class MyProfileComponent implements OnInit {
private closePopup() { private closePopup() {
this.onStateChange(false); this.onStateChange(false);
} }
} }

View File

@@ -1,20 +1,27 @@
<div class="btn-container"> <div class="btn-container">
<div class="input-group mb-3"> <div class="input-group mb-3">
<div class="custom-file"> <div class="custom-file">
<input type="file" <input
class="custom-file-input" type="file"
id="inputProfilePicture" class="custom-file-input"
aria-describedby="inputProfilePicture" id="inputProfilePicture"
(change)="handleFileInput($event)"> aria-describedby="inputProfilePicture"
<label class="custom-file-label" (change)="handleFileInput($event)"
for="inputProfilePicture"> />
{{isProfilePictureSelected ? getFileName() : 'Profile Picture'}} <label class="custom-file-label" for="inputProfilePicture">
{{
isProfilePictureSelected ? getFileName() : "Profile Picture"
}}
</label> </label>
</div> </div>
<div class="input-group-append"> <div class="input-group-append">
<button class="btn btn-outline-secondary" <button
[disabled]="!isProfilePictureSelected" class="btn btn-outline-secondary"
(click)="uploadProfilePicture()">Upload</button> [disabled]="!isProfilePictureSelected"
(click)="uploadProfilePicture()"
>
Upload
</button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -3,21 +3,20 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ProfilePicturePickerComponent } from './profile-picture-picker.component'; import { ProfilePicturePickerComponent } from './profile-picture-picker.component';
describe('ProfilePicturePickerComponent', () => { describe('ProfilePicturePickerComponent', () => {
let component: ProfilePicturePickerComponent; let component: ProfilePicturePickerComponent;
let fixture: ComponentFixture<ProfilePicturePickerComponent>; let fixture: ComponentFixture<ProfilePicturePickerComponent>;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ ProfilePicturePickerComponent ] declarations: [ProfilePicturePickerComponent],
}) }).compileComponents();
.compileComponents();
fixture = TestBed.createComponent(ProfilePicturePickerComponent); fixture = TestBed.createComponent(ProfilePicturePickerComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy();
}); });
}); });

View File

@@ -1,19 +1,18 @@
import {Component, EventEmitter, Output} from '@angular/core'; import { Component, EventEmitter, Output } from '@angular/core';
import {AuthService} from "../../../../shared/auth/auth.service"; import { AuthService } from '../../../../shared/auth/auth.service';
@Component({ @Component({
selector: 'app-profile-picture-picker', selector: 'app-profile-picture-picker',
templateUrl: './profile-picture-picker.component.html', templateUrl: './profile-picture-picker.component.html',
styleUrls: ['./profile-picture-picker.component.css'] styleUrls: ['./profile-picture-picker.component.css'],
}) })
export class ProfilePicturePickerComponent { export class ProfilePicturePickerComponent {
@Output() @Output()
imageSent = new EventEmitter<boolean>(); imageSent = new EventEmitter<boolean>();
private profilePicture!: File; private profilePicture!: File;
constructor(private authService: AuthService) { } constructor(private authService: AuthService) {}
handleFileInput(event: Event) { handleFileInput(event: Event) {
const element = event.currentTarget as HTMLInputElement; const element = event.currentTarget as HTMLInputElement;
@@ -35,5 +34,4 @@ export class ProfilePicturePickerComponent {
get isProfilePictureSelected(): boolean { get isProfilePictureSelected(): boolean {
return !!this.profilePicture; return !!this.profilePicture;
} }
} }

View File

@@ -22,7 +22,7 @@
} }
.auth-body .btn { .auth-body .btn {
background-color: #D8291C !important; background-color: #d8291c !important;
text-decoration: none; text-decoration: none;
border-radius: 8px; border-radius: 8px;
color: #ffffff; color: #ffffff;
@@ -70,7 +70,7 @@
border-bottom: 2px solid #7676769b; border-bottom: 2px solid #7676769b;
} }
.input-div:after { .input-div:after {
content: ''; content: "";
left: 0; left: 0;
right: 0; right: 0;
width: 0; width: 0;
@@ -84,11 +84,12 @@
.input-div:hover:after, .input-div:hover:after,
.input-div:has(input.form-control:focus):after { .input-div:has(input.form-control:focus):after {
width: 100%; width: 100%;
transition: .4s; transition: 0.4s;
overflow: hidden; overflow: hidden;
} }
.input-div > .form-control, .input-div > .form-control:focus { .input-div > .form-control,
.input-div > .form-control:focus {
border: none; border: none;
border-color: inherit; border-color: inherit;
-webkit-box-shadow: none; -webkit-box-shadow: none;
@@ -101,7 +102,7 @@
font-size: 17px; font-size: 17px;
color: #767676; color: #767676;
} }
.input-div >.form-control:-webkit-autofill { .input-div > .form-control:-webkit-autofill {
-webkit-text-fill-color: #767676; -webkit-text-fill-color: #767676;
box-shadow: 0 0 0px 1000px #ffffff inset; box-shadow: 0 0 0px 1000px #ffffff inset;
-webkit-box-shadow: 0 0 0px 1000px #ffffff inset; -webkit-box-shadow: 0 0 0px 1000px #ffffff inset;
@@ -111,17 +112,16 @@
.input-div:hover > .form-control::placeholder, .input-div:hover > .form-control::placeholder,
.input-div:hover > .input-div-icon, .input-div:hover > .input-div-icon,
.input-div:has(input.form-control:focus) > .input-div-icon { .input-div:has(input.form-control:focus) > .input-div-icon {
color: #D8291C; color: #d8291c;
transition: .3s; transition: 0.3s;
} }
.input-div:hover > .form-control::placeholder, .input-div:hover > .form-control::placeholder,
.input-div:focus > .form-control::placeholder{ .input-div:focus > .form-control::placeholder {
font-weight: 500; font-weight: 500;
transition: .3s; transition: 0.3s;
} }
@media (min-width:767px) { @media (min-width: 767px) {
.authentication-container { .authentication-container {
min-width: 630px; min-width: 630px;
} }
@@ -151,5 +151,4 @@
border-right: 2px solid #80808076; border-right: 2px solid #80808076;
height: 100%; height: 100%;
} }
} }

View File

@@ -1,60 +1,81 @@
<app-popup [state]="state" <app-popup
[state]="state"
(stateChange)="onStateChange($event)" (stateChange)="onStateChange($event)"
[ignoreClickOutside]="ignoreClickOutside"> [ignoreClickOutside]="ignoreClickOutside"
>
<div
<div class="container m-0 overflow-hidden" class="container m-0 overflow-hidden"
[@resizeContainerForErrorMessage]="hideErrorMessage()"> [@resizeContainerForErrorMessage]="hideErrorMessage()"
>
<app-error-box [errorMessage]="errorMessage" <app-error-box
[@showErrorMessage]="showErrorMessage()"> [errorMessage]="errorMessage"
[@showErrorMessage]="showErrorMessage()"
>
</app-error-box> </app-error-box>
<div class="container authentication-container" <div
[@hideAuthContainer]="hideErrorMessage()" class="container authentication-container"
(@hideAuthContainer.done)="hideAuthContainer($event)"> [@hideAuthContainer]="hideErrorMessage()"
(@hideAuthContainer.done)="hideAuthContainer($event)"
>
<div class="row"> <div class="row">
<div class="col-lg-6 auth-body auth-body-form"> <div class="col-lg-6 auth-body auth-body-form">
<form [formGroup]="signupForm" (ngSubmit)="onSignUp()"> <form [formGroup]="signupForm" (ngSubmit)="onSignUp()">
<div class="input-div"> <div class="input-div">
<fa-icon class="input-div-icon" <fa-icon
[icon]="_fullnameIcon"> class="input-div-icon"
[icon]="_fullnameIcon"
>
</fa-icon> </fa-icon>
<input type="text" id="fullname" <input
type="text"
id="fullname"
formControlName="fullname" formControlName="fullname"
class="form-control" class="form-control"
placeholder="Full Name"> placeholder="Full Name"
/>
</div> </div>
<div class="input-div"> <div class="input-div">
<fa-icon class="input-div-icon" <fa-icon class="input-div-icon" [icon]="_emailIcon">
[icon]="_emailIcon">
</fa-icon> </fa-icon>
<input type="text" id="email" <input
type="text"
id="email"
formControlName="email" formControlName="email"
class="form-control" class="form-control"
placeholder="Email"> placeholder="Email"
/>
</div> </div>
<div class="input-div"> <div class="input-div">
<fa-icon class="input-div-icon" <fa-icon class="input-div-icon" [icon]="_userIcon">
[icon]="_userIcon">
</fa-icon> </fa-icon>
<input type="text" id="username" <input
type="text"
id="username"
formControlName="username" formControlName="username"
class="form-control" class="form-control"
placeholder="Username"> placeholder="Username"
/>
</div> </div>
<div class="input-div"> <div class="input-div">
<fa-icon class="input-div-icon" <fa-icon
[icon]="_passwordIcon"> class="input-div-icon"
[icon]="_passwordIcon"
>
</fa-icon> </fa-icon>
<input type="password" id="password" <input
type="password"
id="password"
formControlName="password" formControlName="password"
class="form-control" class="form-control"
placeholder="Password"> placeholder="Password"
/>
</div> </div>
<button class="btn" <button
[disabled]="!signupForm.valid" class="btn"
type="submit"> [disabled]="!signupForm.valid"
type="submit"
>
SignUp SignUp
</button> </button>
</form> </form>
@@ -63,24 +84,30 @@
<div class="line"></div> <div class="line"></div>
</div> </div>
<div class="col-lg-6 auth-body auth-body-links"> <div class="col-lg-6 auth-body auth-body-links">
<button mat-button <button
class="oauth-button d-flex justify-content-center align-items-center" mat-button
(click)="onGoogleLogin()"> class="oauth-button d-flex justify-content-center align-items-center"
<mat-icon style="width: 50px; height:30px" (click)="onGoogleLogin()"
svgIcon="google-logo"></mat-icon> >
<mat-icon
style="width: 50px; height: 30px"
svgIcon="google-logo"
></mat-icon>
Login With Google Login With Google
</button> </button>
<button mat-button <button
class="oauth-button d-flex justify-content-center align-items-center" mat-button
(click)="onGithubLogin()"> class="oauth-button d-flex justify-content-center align-items-center"
<mat-icon style="width: 50px; height:30px" (click)="onGithubLogin()"
svgIcon="github-logo"></mat-icon> >
<mat-icon
style="width: 50px; height: 30px"
svgIcon="github-logo"
></mat-icon>
Login With Github Login With Github
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</app-popup> </app-popup>

View File

@@ -8,9 +8,8 @@ describe('SignupComponent', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ SignupComponent ] declarations: [SignupComponent],
}) }).compileComponents();
.compileComponents();
fixture = TestBed.createComponent(SignupComponent); fixture = TestBed.createComponent(SignupComponent);
component = fixture.componentInstance; component = fixture.componentInstance;

View File

@@ -2,21 +2,34 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms'; import { FormControl, FormGroup, Validators } from '@angular/forms';
import { MatIconRegistry } from '@angular/material/icon'; import { MatIconRegistry } from '@angular/material/icon';
import { DomSanitizer } from '@angular/platform-browser'; import { DomSanitizer } from '@angular/platform-browser';
import { faEnvelope, faFingerprint, faLock, faUser } from '@fortawesome/free-solid-svg-icons'; import {
faEnvelope,
faFingerprint,
faLock,
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 { HttpError } from 'src/app/shared/model/httpError/httpError.model'; import { HttpError } from 'src/app/shared/model/httpError/httpError.model';
import HttpErrorChecker from 'src/app/shared/model/httpError/httpErrorChecker'; import HttpErrorChecker from 'src/app/shared/model/httpError/httpErrorChecker';
import UserChecker from 'src/app/shared/model/user/user.checker'; import UserChecker from 'src/app/shared/model/user/user.checker';
import { User } from 'src/app/shared/model/user/user.model'; import { User } from 'src/app/shared/model/user/user.model';
import {animate, animateChild, group, query, state, style, transition, trigger} from "@angular/animations"; import {
import {ValidateEmailValidator} from "../../../shared/validators/validate-email.validator"; animate,
import {ValidatePasswordValidator} from "../../../shared/validators/validate-password.validator"; animateChild,
import {ValidateNotEmptyValidator} from "../../../shared/validators/validate-not-empty.validator"; group,
query,
state,
style,
transition,
trigger,
} from '@angular/animations';
import { ValidateEmailValidator } from '../../../shared/validators/validate-email.validator';
import { ValidatePasswordValidator } from '../../../shared/validators/validate-password.validator';
import { ValidateNotEmptyValidator } from '../../../shared/validators/validate-not-empty.validator';
const GOOGLE_LOGO_SVG = 'assets/img/providers/google.svg';
const GOOGLE_LOGO_SVG = "assets/img/providers/google.svg"; const GITHUB_LOGO_SVG = 'assets/img/providers/github.svg';
const GITHUB_LOGO_SVG = "assets/img/providers/github.svg";
@Component({ @Component({
selector: 'app-signup', selector: 'app-signup',
@@ -24,78 +37,66 @@ const GITHUB_LOGO_SVG = "assets/img/providers/github.svg";
styleUrls: ['./signup.component.css'], styleUrls: ['./signup.component.css'],
animations: [ animations: [
trigger('resizeContainerForErrorMessage', [ trigger('resizeContainerForErrorMessage', [
state('hide', state(
'hide',
style({ style({
height: '100px', height: '100px',
width: '320px', width: '320px',
}) }),
), ),
transition( transition(
'show => hide', 'show => hide',
group([ group([
query( query('@*', animateChild(), { optional: true }),
"@*", animate('1s ease'),
animateChild(), ]),
{ optional: true } ),
),
animate('1s ease')
])
)
]), ]),
trigger('showErrorMessage', [ trigger('showErrorMessage', [
state('show', state(
'show',
style({ style({
opacity: 1, opacity: 1,
height: '100px', height: '100px',
width: '320px', width: '320px',
}) }),
), ),
state('hide', state(
'hide',
style({ style({
opacity: 0, opacity: 0,
height: '0px', height: '0px',
width: '0px', width: '0px',
}) }),
),
transition(
'* => show',
animate(
'500ms ease-in'
)
), ),
transition('* => show', animate('500ms ease-in')),
]), ]),
trigger('hideAuthContainer', [ trigger('hideAuthContainer', [
state('hide', state(
'hide',
style({ style({
opacity: 0, opacity: 0,
}) }),
), ),
transition( transition(
'show => hide', 'show => hide',
group([ group([
query( query('@*', animateChild(), { optional: true }),
"@*", animate('250ms ease-out'),
animateChild(), ]),
{ optional: true } ),
),
animate(
'250ms ease-out'
)
])
)
]), ]),
] ],
}) })
export class SignupComponent implements OnInit { export class SignupComponent implements OnInit {
@Input()
state: boolean = false;
@Input() @Input()
state: boolean = false; ignoreClickOutside!: HTMLDivElement[];
@Input()
ignoreClickOutside!: HTMLDivElement[];
@Output() @Output()
stateChange = new EventEmitter<boolean>(); stateChange = new EventEmitter<boolean>();
signupForm!: FormGroup; signupForm!: FormGroup;
@@ -113,34 +114,46 @@ export class SignupComponent implements OnInit {
_passwordIcon = faLock; _passwordIcon = faLock;
constructor(private authService: AuthService, constructor(
private authService: AuthService,
private matIconRegistry: MatIconRegistry, private matIconRegistry: MatIconRegistry,
private domSanitizer: DomSanitizer) { private domSanitizer: DomSanitizer,
) {
this.matIconRegistry.addSvgIcon( this.matIconRegistry.addSvgIcon(
"google-logo", 'google-logo',
this.domSanitizer.bypassSecurityTrustResourceUrl(GOOGLE_LOGO_SVG) this.domSanitizer.bypassSecurityTrustResourceUrl(GOOGLE_LOGO_SVG),
); );
this.matIconRegistry.addSvgIcon( this.matIconRegistry.addSvgIcon(
"github-logo", 'github-logo',
this.domSanitizer.bypassSecurityTrustResourceUrl(GITHUB_LOGO_SVG) this.domSanitizer.bypassSecurityTrustResourceUrl(GITHUB_LOGO_SVG),
); );
} }
ngOnInit(): void { ngOnInit(): void {
this.signupForm = new FormGroup({ this.signupForm = new FormGroup({
'fullname': new FormControl(null, [Validators.required, ValidateNotEmptyValidator]), fullname: new FormControl(null, [
Validators.required,
ValidateNotEmptyValidator,
]),
// Create a Email Validator // Create a Email Validator
'email': new FormControl(null, [Validators.required, ValidateEmailValidator]), email: new FormControl(null, [
'username': new FormControl(null, [Validators.required, ValidateNotEmptyValidator]), Validators.required,
ValidateEmailValidator,
]),
username: new FormControl(null, [
Validators.required,
ValidateNotEmptyValidator,
]),
// Create a Password Validator // Create a Password Validator
'password': new FormControl(null, [Validators.required, ValidatePasswordValidator]) password: new FormControl(null, [
Validators.required,
ValidatePasswordValidator,
]),
}); });
this.errorMessage = null; this.errorMessage = null;
this.authSubject = this.authService.authSubject.subscribe( this.authSubject = this.authService.authSubject.subscribe((res) => {
res => { this.validateSignup(res);
this.validateSignup(res); });
}
);
} }
onStateChange(state: boolean) { onStateChange(state: boolean) {
@@ -152,8 +165,8 @@ export class SignupComponent implements OnInit {
name: this.signupForm.controls['fullname'].value, name: this.signupForm.controls['fullname'].value,
email: this.signupForm.controls['email'].value, email: this.signupForm.controls['email'].value,
username: this.signupForm.controls['username'].value, username: this.signupForm.controls['username'].value,
password: this.signupForm.controls['password'].value password: this.signupForm.controls['password'].value,
} };
this.authService.signup(user); this.authService.signup(user);
} }
@@ -167,11 +180,11 @@ export class SignupComponent implements OnInit {
private validateSignup(res: User | HttpError | null) { private validateSignup(res: User | HttpError | null) {
if (res && UserChecker.test(res)) { if (res && UserChecker.test(res)) {
this.closePopup() this.closePopup();
} if (HttpErrorChecker.test(res)) { }
if (HttpErrorChecker.test(res)) {
this.errorMessage = (<HttpError>res).details; this.errorMessage = (<HttpError>res).details;
} }
} }
private closePopup() { private closePopup() {
@@ -181,24 +194,22 @@ export class SignupComponent implements OnInit {
public showErrorMessage(): string { public showErrorMessage(): string {
if (this.isShowErrorMessage) { if (this.isShowErrorMessage) {
return "show"; return 'show';
} }
return "hide"; return 'hide';
} }
public hideErrorMessage(): string { public hideErrorMessage(): string {
if (!!this.errorMessage) { if (!!this.errorMessage) {
return "hide"; return 'hide';
} }
return "show"; return 'show';
} }
hideAuthContainer(event: any) { hideAuthContainer(event: any) {
if (event.toState === "hide") { if (event.toState === 'hide') {
event.element.style.display = "none"; event.element.style.display = 'none';
this.isShowErrorMessage = true; this.isShowErrorMessage = true;
} }
} }
} }

View File

@@ -1,8 +1,10 @@
<div class="slider" <div
appClickedOutside class="slider"
[ignoreElementList]="ignoreClickOutside" appClickedOutside
[clickOutsideStopWatching]="clickOutsideStopWatching" [ignoreElementList]="ignoreClickOutside"
(clickOutside)="closeNavSlider()" [clickOutsideStopWatching]="clickOutsideStopWatching"
[@slideState]="sliderStatus"> (clickOutside)="closeNavSlider()"
[@slideState]="sliderStatus"
>
<ng-content></ng-content> <ng-content></ng-content>
</div> </div>

View File

@@ -8,9 +8,8 @@ describe('HeaderSliderComponent', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ HeaderSliderComponent ] declarations: [HeaderSliderComponent],
}) }).compileComponents();
.compileComponents();
fixture = TestBed.createComponent(HeaderSliderComponent); fixture = TestBed.createComponent(HeaderSliderComponent);
component = fixture.componentInstance; component = fixture.componentInstance;

View File

@@ -1,58 +1,62 @@
import { animate, animateChild, group, query, state, style, transition, trigger } from '@angular/animations'; import {
animate,
animateChild,
group,
query,
state,
style,
transition,
trigger,
} from '@angular/animations';
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
@Component({ @Component({
selector: 'app-header-slider', selector: 'app-header-slider',
templateUrl: './header-slider.component.html', templateUrl: './header-slider.component.html',
styleUrls: ['./header-slider.component.css'], styleUrls: ['./header-slider.component.css'],
animations:[ animations: [
trigger('slideState', [ trigger('slideState', [
state('hide', style({ state(
transform: 'translateX(100%)' 'hide',
})), style({
state('show', style({ transform: 'translateX(100%)',
transform: 'translateX(0%)' }),
})), ),
transition( state(
'hide => show', [ 'show',
group([ style({
query( transform: 'translateX(0%)',
"@*", }),
animateChild(), ),
{ optional: true } transition('hide => show', [
), group([
animate('600ms ease-in') query('@*', animateChild(), { optional: true }),
]) animate('600ms ease-in'),
]), ]),
transition( ]),
'show => hide', [ transition('show => hide', [
group([ group([
query( query('@*', animateChild(), { optional: true }),
"@*", animate('500ms ease-out'),
animateChild(), ]),
{ optional: true } ]),
), ]),
animate('500ms ease-out') ],
])
])
])
]
}) })
export class HeaderSliderComponent { export class HeaderSliderComponent {
@Input()
ignoreClickOutside!: HTMLDivElement[];
@Input() @Input()
ignoreClickOutside!: HTMLDivElement[]; clickOutsideStopWatching: boolean = false;
@Input() @Input()
clickOutsideStopWatching: boolean = false; state: boolean = false;
@Input()
state: boolean = false;
@Output() @Output()
stateChange = new EventEmitter<boolean>(); stateChange = new EventEmitter<boolean>();
constructor() { } constructor() {}
get sliderStatus() { get sliderStatus() {
return this.state ? 'show' : 'hide'; return this.state ? 'show' : 'hide';
@@ -64,7 +68,6 @@ export class HeaderSliderComponent {
} }
public changeState() { public changeState() {
this.stateChange.emit(this.state) this.stateChange.emit(this.state);
} }
} }

View File

@@ -29,15 +29,14 @@
.nav-links li a { .nav-links li a {
text-decoration: none; text-decoration: none;
font-family: 'Montserrat'; font-family: "Montserrat";
font-weight: 400; font-weight: 400;
color: #ffffff; color: #ffffff;
font-size: 18px; font-size: 18px;
} }
.nav-links li a:hover { .nav-links li a:hover {
opacity: .8; opacity: 0.8;
color: #f44336; color: #f44336;
transition: 0.5s; transition: 0.5s;
} }

View File

@@ -1,9 +1,18 @@
<div class="links-container"> <div class="links-container">
<div class="nav-links"> <div class="nav-links">
<ul> <ul>
<li *ngFor="let nav of navLink; let i=index"[@animateSliderItem]="{ value: itemStatus, params: { fadeInTime: .6 + i/20 , fadeOutTime: .6 - i/10 } }"> <li
*ngFor="let nav of navLink; let i = index"
[@animateSliderItem]="{
value: itemStatus,
params: {
fadeInTime: 0.6 + i / 20,
fadeOutTime: 0.6 - i / 10
}
}"
>
<a [routerLink]="nav.link"> <a [routerLink]="nav.link">
{{nav.page}} {{ nav.page }}
</a> </a>
</li> </li>
</ul> </ul>
@@ -11,18 +20,32 @@
</div> </div>
<div class="profile-container"> <div class="profile-container">
<div class="profile" <div
[@animateSliderItem]="{ value: itemStatus, params: { fadeInTime: .6 + (navLink.length+1)/20 , fadeOutTime: .6 - (navLink.length+1)/10 } }" class="profile"
#profile> [@animateSliderItem]="{
<div class="profile-btn" value: itemStatus,
(click)="onProfileButtonClicked()"> params: {
<fa-icon *ngIf="!loggedUser || !(loggedUser.profilePictureUrl)" fadeInTime: 0.6 + (navLink.length + 1) / 20,
class="fas fa-user" [icon]="userIcon"></fa-icon> fadeOutTime: 0.6 - (navLink.length + 1) / 10
<img *ngIf="!!loggedUser && !!(loggedUser.profilePictureUrl)" }
class="profile-picture" }"
[ngSrc]="loggedUser.profilePictureUrl" #profile
width="50" height="50" >
alt="Profile Picture" priority/> <div class="profile-btn" (click)="onProfileButtonClicked()">
<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>

View File

@@ -8,9 +8,8 @@ describe('NavSliderComponent', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ NavSliderComponent ] declarations: [NavSliderComponent],
}) }).compileComponents();
.compileComponents();
fixture = TestBed.createComponent(NavSliderComponent); fixture = TestBed.createComponent(NavSliderComponent);
component = fixture.componentInstance; component = fixture.componentInstance;

View File

@@ -1,33 +1,41 @@
import {Component, EventEmitter, OnDestroy, 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 UserChecker from '../../../shared/model/user/user.checker';
import {User} from "../../../shared/model/user/user.model"; import { User } from '../../../shared/model/user/user.model';
import {AuthService} from "../../../shared/auth/auth.service"; import { AuthService } from '../../../shared/auth/auth.service';
import {Subscription} from "rxjs"; 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 implements OnInit, OnDestroy { export class NavSliderComponent
extends SliderItemComponent
implements OnInit, OnDestroy
{
userIcon = faUser; userIcon = faUser;
navLink = [ navLink = [
{ page: "Home", link: "/home" }, { page: 'Home', link: '/home' },
{ page: "Work", link: "/home" }, { page: 'Work', link: '/home' },
{ page: "Contact", link: "/home" }, { page: 'Contact', link: '/home' },
{ page: "About", link: "/home" } { page: 'About', link: '/home' },
] ];
loggedUser!: User | null; loggedUser!: User | null;
private userSubscription!: Subscription; private userSubscription!: Subscription;
@Output() @Output()
profileButtonClicked = new EventEmitter(); profileButtonClicked = new EventEmitter();
constructor(private authService: AuthService) { constructor(private authService: AuthService) {
super(); super();
@@ -35,14 +43,14 @@ 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) => {
if (res && UserChecker.test(res)) { if (res && UserChecker.test(res)) {
this.loggedUser = <User>res; this.loggedUser = <User>res;
} else { } else {
this.loggedUser = null; this.loggedUser = null;
} }
} },
) );
} }
ngOnDestroy(): void { ngOnDestroy(): void {
@@ -52,5 +60,4 @@ export class NavSliderComponent extends SliderItemComponent implements OnInit, O
onProfileButtonClicked() { onProfileButtonClicked() {
this.profileButtonClicked.emit(); this.profileButtonClicked.emit();
} }
} }

View File

@@ -28,17 +28,16 @@
} }
.user-options li a { .user-options li a {
font-family: 'Montserrat'; font-family: "Montserrat";
text-decoration: none; text-decoration: none;
font-weight: 400; font-weight: 400;
color: #ffffff; color: #ffffff;
font-size: 18px; font-size: 18px;
} }
.user-options li a:hover { .user-options li a:hover {
color: #f44336; color: #f44336;
transition: 0.5s; transition: 0.5s;
cursor: pointer; cursor: pointer;
opacity: .8; opacity: 0.8;
} }

View File

@@ -1,9 +1,21 @@
<div class="user-container"> <div class="user-container">
<div class="user-options"> <div class="user-options">
<ul> <ul>
<li *ngFor="let options of (user ? userOptions : userlessOptions); let i=index"[@animateSliderItem]="{ value: itemStatus, params: { fadeInTime: .6 + i/10 , fadeOutTime: .6 - i/10 } }"> <li
*ngFor="
let options of user ? userOptions : userlessOptions;
let i = index
"
[@animateSliderItem]="{
value: itemStatus,
params: {
fadeInTime: 0.6 + i / 10,
fadeOutTime: 0.6 - i / 10
}
}"
>
<a (click)="options.onClick()"> <a (click)="options.onClick()">
{{options.name}} {{ options.name }}
</a> </a>
</li> </li>
</ul> </ul>

View File

@@ -8,9 +8,8 @@ describe('UserSliderComponent', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ UserSliderComponent ] declarations: [UserSliderComponent],
}) }).compileComponents();
.compileComponents();
fixture = TestBed.createComponent(UserSliderComponent); fixture = TestBed.createComponent(UserSliderComponent);
component = fixture.componentInstance; component = fixture.componentInstance;

View File

@@ -8,77 +8,75 @@ import { User } from 'src/app/shared/model/user/user.model';
@Component({ @Component({
selector: 'app-user-slider', selector: 'app-user-slider',
templateUrl: './user-slider.component.html', templateUrl: './user-slider.component.html',
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.onLoginOptionClicked(); this.onLoginOptionClicked();
} },
}, },
{ {
name: "Signup", name: 'Signup',
onClick: () => { onClick: () => {
this.onSignUpOptionClick(); this.onSignUpOptionClick();
} },
} },
] ];
userOptions = [ userOptions = [
{ {
name: "My Profile", name: 'My Profile',
onClick: () => { onClick: () => {
this.onMyProfileClicked() this.onMyProfileClicked();
} },
}, },
{ {
name: "Help", name: 'Help',
onClick: () => { onClick: () => {
this.onHelpClicked(); this.onHelpClicked();
} },
}, },
{ {
name: "Logout", name: 'Logout',
onClick: () => { onClick: () => {
this.onLogout(); this.onLogout();
} },
} },
] ];
user!: User|null; user!: User | null;
authSubscription!: Subscription; authSubscription!: Subscription;
@Output() @Output()
loginPopupState: EventEmitter<boolean> = new EventEmitter(); loginPopupState: EventEmitter<boolean> = new EventEmitter();
@Output() @Output()
signupPopupState: EventEmitter<boolean> = new EventEmitter(); signupPopupState: EventEmitter<boolean> = new EventEmitter();
@Output() @Output()
helpPopupState: EventEmitter<boolean> = new EventEmitter(); helpPopupState: EventEmitter<boolean> = new EventEmitter();
@Output() @Output()
myProfilePopupState: EventEmitter<boolean> = new EventEmitter(); 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)) { this.user = <User>res;
this.user = <User>res; } else {
} else { this.user = null;
this.user = null;
}
} }
) },
);
} }
onLoginOptionClicked(): void { onLoginOptionClicked(): void {
@@ -100,5 +98,4 @@ export class UserSliderComponent extends SliderItemComponent implements OnInit {
onLogout() { onLogout() {
this.authService.logout(); this.authService.logout();
} }
} }

View File

@@ -48,20 +48,20 @@
height: 5px; height: 5px;
background: #ffffff; background: #ffffff;
border-radius: 5px; border-radius: 5px;
box-shadow: 0 2px 5px rgba(255, 101, 47, .2); box-shadow: 0 2px 5px rgba(255, 101, 47, 0.2);
transition: all .5s ease-in-out; transition: all 0.5s ease-in-out;
} }
.burger-menu::before, .burger-menu::before,
.burger-menu::after { .burger-menu::after {
content: ''; content: "";
position: absolute; position: absolute;
width: 40px; width: 40px;
height: 5px; height: 5px;
background: #ffffff; background: #ffffff;
border-radius: 5px; border-radius: 5px;
box-shadow: 0 2px 5px rgba(255, 101, 47, .2); box-shadow: 0 2px 5px rgba(255, 101, 47, 0.2);
transition: all .5s ease-in-out; transition: all 0.5s ease-in-out;
} }
.burger-menu::before { .burger-menu::before {
@@ -75,17 +75,17 @@
.burger-menu.open { .burger-menu.open {
background: transparent; background: transparent;
box-shadow: none; box-shadow: none;
transition: all .5s ease-in-out; transition: all 0.5s ease-in-out;
} }
.burger-menu.open::before { .burger-menu.open::before {
transform: rotate(45deg); transform: rotate(45deg);
transition: all .5s ease-in-out; transition: all 0.5s ease-in-out;
} }
.burger-menu.open::after { .burger-menu.open::after {
transform: rotate(-45deg); transform: rotate(-45deg);
transition: all .5s ease-in-out; transition: all 0.5s ease-in-out;
} }
.profile { .profile {
@@ -99,7 +99,6 @@ app-header-slider {
/* ====================== COMPUTER MEDIA FORMAT ======================== */ /* ====================== COMPUTER MEDIA FORMAT ======================== */
@media only screen and (min-width: 712px) { @media only screen and (min-width: 712px) {
.nav-links { .nav-links {
all: unset; all: unset;
width: 50%; width: 50%;
@@ -121,7 +120,7 @@ app-header-slider {
} }
.link-container li a { .link-container li a {
font-family: 'Montserrat', sans-serif; font-family: "Montserrat", sans-serif;
text-decoration: none; text-decoration: none;
letter-spacing: 3px; letter-spacing: 3px;
color: #ffffff; color: #ffffff;
@@ -180,5 +179,4 @@ app-header-slider {
app-header-slider { app-header-slider {
opacity: 0; opacity: 0;
} }
} }

View File

@@ -2,7 +2,7 @@
<div class="main" #header> <div class="main" #header>
<div class="logo"> <div class="logo">
<a routerLink=""> <a routerLink="">
<img src="assets/img/logohideyoshi-white.png" alt=""> <img src="assets/img/logohideyoshi-white.png" alt="" />
</a> </a>
</div> </div>
<div class="nav-links"> <div class="nav-links">
@@ -15,14 +15,25 @@
</div> </div>
<div class="profile" #profileDropdown> <div class="profile" #profileDropdown>
<div class="profile-btn" (click)="toogleProfileDropdown()" #profileBtn> <div
<fa-icon *ngIf="!loggedUser || !(loggedUser.profilePictureUrl)" class="profile-btn"
class="fas fa-user" [icon]="userIcon"></fa-icon> (click)="toogleProfileDropdown()"
<img *ngIf="!!loggedUser && !!(loggedUser.profilePictureUrl)" #profileBtn
class="profile-picture" >
[ngSrc]="loggedUser.profilePictureUrl" <fa-icon
width="50" height="50" *ngIf="!loggedUser || !loggedUser.profilePictureUrl"
alt="Profile Picture" priority/> 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
@@ -33,36 +44,43 @@
(loginPopupState)="loginPopupStateChange($event)" (loginPopupState)="loginPopupStateChange($event)"
(signupPopupState)="signupPopupStateChange($event)" (signupPopupState)="signupPopupStateChange($event)"
(myProfilePopupState)="myProfilePopupStateChange($event)" (myProfilePopupState)="myProfilePopupStateChange($event)"
(helpPopupState)="helpPopupStateChange($event)"> (helpPopupState)="helpPopupStateChange($event)"
>
</app-header-dropdown> </app-header-dropdown>
</div> </div>
<div class="burger-container" (click)="toogleNavSlider()"> <div class="burger-container" (click)="toogleNavSlider()">
<div class="burger-menu" [ngClass]="{'open' : navSliderStatus}"> <div
</div> class="burger-menu"
[ngClass]="{ open: navSliderStatus }"
></div>
</div> </div>
</div> </div>
<div #nav> <div #nav>
<app-header-slider <app-header-slider
[(state)]="navSliderStatus" [(state)]="navSliderStatus"
[clickOutsideStopWatching]="userSliderStatus" [clickOutsideStopWatching]="userSliderStatus"
[ignoreClickOutside]="[header, user]"> [ignoreClickOutside]="[header, user]"
>
<app-nav-slider <app-nav-slider
[state]="navSliderStatus" [state]="navSliderStatus"
(profileButtonClicked)="profileButtonClicked()"> (profileButtonClicked)="profileButtonClicked()"
>
</app-nav-slider> </app-nav-slider>
</app-header-slider> </app-header-slider>
</div> </div>
<div #user> <div #user>
<app-header-slider <app-header-slider
[(state)]="userSliderStatus" [(state)]="userSliderStatus"
[ignoreClickOutside]="[header, nav]"> [ignoreClickOutside]="[header, nav]"
>
<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)" (myProfilePopupState)="myProfilePopupStateChange($event)"
(helpPopupState)="helpPopupStateChange($event)"> (helpPopupState)="helpPopupStateChange($event)"
>
</app-user-slider> </app-user-slider>
</app-header-slider> </app-header-slider>
</div> </div>

View File

@@ -8,9 +8,8 @@ describe('HeaderComponent', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ HeaderComponent ] declarations: [HeaderComponent],
}) }).compileComponents();
.compileComponents();
fixture = TestBed.createComponent(HeaderComponent); fixture = TestBed.createComponent(HeaderComponent);
component = fixture.componentInstance; component = fixture.componentInstance;

View File

@@ -1,21 +1,28 @@
import {Component, ComponentRef, ElementRef, OnDestroy, 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 { 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 { 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';
@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 implements OnInit, OnDestroy { export class HeaderComponent implements OnInit, OnDestroy {
userIcon = faUser; userIcon = faUser;
profileDropdownState: boolean = false; profileDropdownState: boolean = false;
@@ -26,13 +33,13 @@ export class HeaderComponent implements OnInit, OnDestroy {
userSliderStatus: boolean = false; userSliderStatus: boolean = false;
@ViewChild('profileBtn') @ViewChild('profileBtn')
profileBtnElementRef!: ElementRef; profileBtnElementRef!: ElementRef;
@ViewChild('profileDropdown') @ViewChild('profileDropdown')
profileDropdownElementRef!: ElementRef; profileDropdownElementRef!: ElementRef;
@ViewChild('user') @ViewChild('user')
userElementRef!: ElementRef; userElementRef!: ElementRef;
loggedUser!: User | null; loggedUser!: User | null;
@@ -46,25 +53,27 @@ export class HeaderComponent implements OnInit, OnDestroy {
private helpComponent!: ComponentRef<HelpComponent>; 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) => {
if (res && UserChecker.test(res)) { if (res && UserChecker.test(res)) {
this.loggedUser = <User>res; this.loggedUser = <User>res;
} else { } else {
this.loggedUser = null; this.loggedUser = null;
} }
} },
) );
} }
ngOnDestroy(): void { ngOnDestroy(): void {
this.userSubscription.unsubscribe(); this.userSubscription.unsubscribe();
} }
public toogleProfileDropdown(): void { public toogleProfileDropdown(): void {
this.profileDropdownState = !this.profileDropdownState; this.profileDropdownState = !this.profileDropdownState;
} }
@@ -121,22 +130,21 @@ export class HeaderComponent implements OnInit, OnDestroy {
} }
private createLoginPopup(): void { private createLoginPopup(): void {
this.loginComponent = this.viewContainerRef.createComponent(LoginComponent); this.loginComponent =
this.viewContainerRef.createComponent(LoginComponent);
this.loginComponent.instance.state = true; this.loginComponent.instance.state = true;
this.loginComponent.instance.ignoreClickOutside = [ this.loginComponent.instance.ignoreClickOutside = [
this.profileBtnElementRef, this.profileBtnElementRef,
this.profileDropdownElementRef, this.profileDropdownElementRef,
this.userElementRef this.userElementRef,
].map(element => element.nativeElement); ].map((element) => element.nativeElement);
this.loginComponent.instance.stateChange.subscribe( this.loginComponent.instance.stateChange.subscribe((state) => {
state => { if (!state) {
if (!state) { this.closeLoginPopup();
this.closeLoginPopup()
}
} }
); });
this.navSliderStatus = false; this.navSliderStatus = false;
this.userSliderStatus = false; this.userSliderStatus = false;
@@ -144,22 +152,21 @@ export class HeaderComponent implements OnInit, OnDestroy {
} }
private createSignupPopup() { private createSignupPopup() {
this.signupComponent = this.viewContainerRef.createComponent(SignupComponent); this.signupComponent =
this.viewContainerRef.createComponent(SignupComponent);
this.signupComponent.instance.state = true; this.signupComponent.instance.state = true;
this.signupComponent.instance.ignoreClickOutside = [ this.signupComponent.instance.ignoreClickOutside = [
this.profileBtnElementRef, this.profileBtnElementRef,
this.profileDropdownElementRef, this.profileDropdownElementRef,
this.userElementRef this.userElementRef,
].map(element => element.nativeElement); ].map((element) => element.nativeElement);
this.signupComponent.instance.stateChange.subscribe( this.signupComponent.instance.stateChange.subscribe((state) => {
state => { if (!state) {
if (!state) { this.closeSignupPopup();
this.closeSignupPopup()
}
} }
); });
this.navSliderStatus = false; this.navSliderStatus = false;
this.userSliderStatus = false; this.userSliderStatus = false;
@@ -167,17 +174,16 @@ export class HeaderComponent implements OnInit, OnDestroy {
} }
private createMyProfilePopup() { private createMyProfilePopup() {
this.myProfileComponent = this.viewContainerRef.createComponent(MyProfileComponent); this.myProfileComponent =
this.viewContainerRef.createComponent(MyProfileComponent);
this.myProfileComponent.instance.state = true; this.myProfileComponent.instance.state = true;
this.myProfileComponent.instance.user = this.loggedUser; this.myProfileComponent.instance.user = this.loggedUser;
this.myProfileComponent.instance.stateChange.subscribe( this.myProfileComponent.instance.stateChange.subscribe((state) => {
state => { if (!state) {
if (!state) { this.closeMyProfilePopup();
this.closeMyProfilePopup()
}
} }
); });
this.navSliderStatus = false; this.navSliderStatus = false;
this.userSliderStatus = false; this.userSliderStatus = false;
@@ -185,16 +191,15 @@ export class HeaderComponent implements OnInit, OnDestroy {
} }
private createHelpPopup() { private createHelpPopup() {
this.helpComponent = this.viewContainerRef.createComponent(HelpComponent); this.helpComponent =
this.viewContainerRef.createComponent(HelpComponent);
this.helpComponent.instance.state = true; this.helpComponent.instance.state = true;
this.helpComponent.instance.stateChange.subscribe( this.helpComponent.instance.stateChange.subscribe((state) => {
state => { if (!state) {
if (!state) { this.closeHelpPopup();
this.closeHelpPopup()
}
} }
); });
this.navSliderStatus = false; this.navSliderStatus = false;
this.userSliderStatus = false; this.userSliderStatus = false;

View File

@@ -1,6 +1,6 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import {CommonModule, NgOptimizedImage} from '@angular/common'; import { CommonModule, NgOptimizedImage } from '@angular/common';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations' import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HeaderComponent } from './header.component'; import { HeaderComponent } from './header.component';
import { HeaderSliderComponent } from './header-slider/header-slider.component'; import { HeaderSliderComponent } from './header-slider/header-slider.component';
@@ -14,14 +14,12 @@ import { LoginComponent } from './header-popup/login/login.component';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { SignupComponent } from './header-popup/signup/signup.component'; 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 { 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'; import { ProfilePicturePickerComponent } from './header-popup/my-profile/profile-picture-picker/profile-picture-picker.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
HeaderComponent, HeaderComponent,
@@ -46,12 +44,13 @@ import { ProfilePicturePickerComponent } from './header-popup/my-profile/profile
ReactiveFormsModule, ReactiveFormsModule,
MatIconModule, MatIconModule,
SharedModule, SharedModule,
NgOptimizedImage NgOptimizedImage,
], exports: [ ],
exports: [
HeaderComponent, HeaderComponent,
HeaderSliderComponent, HeaderSliderComponent,
NavSliderComponent, NavSliderComponent,
UserSliderComponent UserSliderComponent,
] ],
}) })
export class HeaderModule { } export class HeaderModule {}

View File

@@ -8,9 +8,8 @@ describe('HomeComponent', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ HomeComponent ] declarations: [HomeComponent],
}) }).compileComponents();
.compileComponents();
fixture = TestBed.createComponent(HomeComponent); fixture = TestBed.createComponent(HomeComponent);
component = fixture.componentInstance; component = fixture.componentInstance;

View File

@@ -3,10 +3,8 @@ import { Component, OnInit } from '@angular/core';
@Component({ @Component({
selector: 'app-home', selector: 'app-home',
templateUrl: './home.component.html', templateUrl: './home.component.html',
styleUrls: ['./home.component.css'] styleUrls: ['./home.component.css'],
}) })
export class HomeComponent { export class HomeComponent {
constructor() {}
constructor() { }
} }

View File

@@ -1,17 +1,25 @@
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, map, Observable, of, Subject, take, tap} 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 { User } from '../model/user/user.model'; import { User } from '../model/user/user.model';
import * as http from "http"; import * as http from 'http';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root',
}) })
export class AuthService { export class AuthService {
private userAuthenticated!: User; private userAuthenticated!: User;
authSubject = new Subject<User | HttpError | null>(); authSubject = new Subject<User | HttpError | null>();
@@ -20,30 +28,35 @@ 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));
} }
googleLogin() { googleLogin() {
window.open(this.BACKEND_OAUTH_PATH + '/oauth2/authorization/google', '_self'); window.open(
this.BACKEND_OAUTH_PATH + '/oauth2/authorization/google',
'_self',
);
} }
githubLogin() { githubLogin() {
window.open(this.BACKEND_OAUTH_PATH + '/oauth2/authorization/github', '_self'); window.open(
this.BACKEND_OAUTH_PATH + '/oauth2/authorization/github',
'_self',
);
} }
loginGoogleUser(p: any): void { loginGoogleUser(p: any): void {
this.validateUser(this.fetchGoogleOAuthToken(p)) this.validateUser(this.fetchGoogleOAuthToken(p));
} }
loginGithubUser(p: any): void { loginGithubUser(p: any): void {
this.validateUser(this.fetchGithubOAuthToken(p)) this.validateUser(this.fetchGithubOAuthToken(p));
} }
signup(userAuthAtempt: User): void { signup(userAuthAtempt: User): void {
this.validateUser(this.createUser(userAuthAtempt)); this.validateUser(this.createUser(userAuthAtempt));
} }
refresh(): void { refresh(): void {
@@ -56,7 +69,7 @@ export class AuthService {
logout() { logout() {
this.authSubject.next(null); this.authSubject.next(null);
this.destroySessions().subscribe() this.destroySessions().subscribe();
} }
deleteAccount() { deleteAccount() {
@@ -66,7 +79,7 @@ export class AuthService {
addProfilePicture(file: File): void { addProfilePicture(file: File): void {
const fileType = file.type.split('/')[1]; const fileType = file.type.split('/')[1];
this.getAddProfilePictureUrl(fileType).subscribe({ this.getAddProfilePictureUrl(fileType).subscribe({
next: (url: string|null) => { next: (url: string | null) => {
if (url != null) { if (url != null) {
this.uploadProfilePicture(url, file).then( this.uploadProfilePicture(url, file).then(
(response: Observable<any>) => { (response: Observable<any>) => {
@@ -75,169 +88,160 @@ export class AuthService {
this.processProfilePicture().subscribe( this.processProfilePicture().subscribe(
() => { () => {
this.refresh(); this.refresh();
} },
); );
} },
}); });
} },
); );
} }
} },
}) });
} }
private loginUser(userAuthAtempt: User): Observable<User|any> { private loginUser(userAuthAtempt: User): Observable<User | any> {
let loginParams = new URLSearchParams(); let loginParams = new URLSearchParams();
loginParams.set("username", userAuthAtempt.username!); loginParams.set('username', userAuthAtempt.username!);
loginParams.set("password", userAuthAtempt.password!); loginParams.set('password', userAuthAtempt.password!);
let headers = new HttpHeaders({ let headers = new HttpHeaders({
'Content-Type': 'application/x-www-form-urlencoded' 'Content-Type': 'application/x-www-form-urlencoded',
}); });
return this.http.post<User>( return this.http
this.BACKEND_PATH + "/user/login", .post<User>(this.BACKEND_PATH + '/user/login', loginParams, {
loginParams, headers: headers,
{ headers: headers, withCredentials: true } withCredentials: true,
).pipe( })
first() .pipe(first());
)
} }
private fetchGoogleOAuthToken(p: any): Observable<User|any> { private fetchGoogleOAuthToken(p: any): Observable<User | any> {
let params = new HttpParams({
fromObject: p,
});
let params = new HttpParams( return this.http
{ .get<User>(this.BACKEND_OAUTH_PATH + '/login/oauth2/code/google', {
fromObject: p
}
);
return this.http.get<User>(
this.BACKEND_OAUTH_PATH + '/login/oauth2/code/google',
{
withCredentials: true, withCredentials: true,
params: params params: params,
}, })
).pipe( .pipe(first());
first()
);
} }
private fetchGithubOAuthToken(p: any): Observable<User|any> { private fetchGithubOAuthToken(p: any): Observable<User | any> {
let params = new HttpParams({
fromObject: p,
});
let params = new HttpParams( return this.http
{ .get<User>(this.BACKEND_OAUTH_PATH + '/login/oauth2/code/github', {
fromObject: p
}
);
return this.http.get<User>(
this.BACKEND_OAUTH_PATH + '/login/oauth2/code/github',
{
withCredentials: true, withCredentials: true,
params: params params: params,
}, })
).pipe( .pipe(first());
first()
);
} }
private createUser(newUser: User) { private createUser(newUser: User) {
return this.http.post<User>( return this.http
this.BACKEND_PATH + "/user/signup", .post<User>(this.BACKEND_PATH + '/user/signup', newUser, {
newUser, withCredentials: true,
{ withCredentials: true } })
).pipe( .pipe(first());
first()
)
} }
private refreshAccessToken() { private refreshAccessToken() {
return this.http.post<User>( return this.http.post<User>(
this.BACKEND_PATH + "/user/login/refresh", this.BACKEND_PATH + '/user/login/refresh',
this.userAuthenticated.refreshToken, this.userAuthenticated.refreshToken,
{ withCredentials: true } { withCredentials: true },
); );
} }
private validateSession(): Observable<User> { private validateSession(): Observable<User> {
return this.http.get<User>( return this.http.get<User>(this.BACKEND_PATH + '/session/validate', {
this.BACKEND_PATH + '/session/validate', withCredentials: true,
{ withCredentials: true }
);
}
private destroySessions() {
return this.http.delete(
this.BACKEND_PATH + '/session/destroy',
{ withCredentials: true }
);
}
private deleteAccountRequest() {
let headers = this.createAuthorizationHeader()
return this.http.delete(
this.BACKEND_PATH + `/user/delete`,
{ headers: headers, withCredentials: true }
);
}
private validateUser(userAuthAtempt: Observable<User>) {
userAuthAtempt.pipe(
catchError(error => {
if (error.status == 0) {
return of(<HttpError>{
title: "Service Unavailable",
status: 500,
details: "Service Unavailable, please try again later.",
developerMessage: "Service Unavailable, please try again later.",
timestamp: new Date().toISOString()
});
}
return of(<HttpError>error.error);
}),
first()
).subscribe({
next: userAuthentication => {
this.userAuthenticated = <User>userAuthentication;
this.authSubject.next(this.userAuthenticated);
}
}); });
} }
private getAddProfilePictureUrl(fileType: string): Observable<string|null> { private destroySessions() {
return this.http.post<{ presigned_url: string, file_key: string }>( return this.http.delete(this.BACKEND_PATH + '/session/destroy', {
this.BACKEND_PATH + '/user/profile-picture?fileType=' + fileType, withCredentials: true,
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>> { private deleteAccountRequest() {
let headers = this.createAuthorizationHeader();
return this.http.delete(this.BACKEND_PATH + `/user/delete`, {
headers: headers,
withCredentials: true,
});
}
private validateUser(userAuthAtempt: Observable<User>) {
userAuthAtempt
.pipe(
catchError((error) => {
if (error.status == 0) {
return of(<HttpError>{
title: 'Service Unavailable',
status: 500,
details:
'Service Unavailable, please try again later.',
developerMessage:
'Service Unavailable, please try again later.',
timestamp: new Date().toISOString(),
});
}
return of(<HttpError>error.error);
}),
first(),
)
.subscribe({
next: (userAuthentication) => {
this.userAuthenticated = <User>userAuthentication;
this.authSubject.next(this.userAuthenticated);
},
});
}
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); const fileData = await this.readAsArrayBuffer(file);
let headers = new HttpHeaders({ let headers = new HttpHeaders({
'Content-Type': file.type 'Content-Type': file.type,
}) });
return this.http.put( return this.http.put(url, fileData, {
url, headers: headers,
fileData, });
{
headers: headers,
}
);
} }
private processProfilePicture() { private processProfilePicture() {
@@ -246,21 +250,22 @@ export class AuthService {
null, null,
{ {
headers: this.createAuthorizationHeader(), headers: this.createAuthorizationHeader(),
withCredentials: true withCredentials: true,
} },
) );
} }
private createAuthorizationHeader(): HttpHeaders { private createAuthorizationHeader(): HttpHeaders {
return new HttpHeaders({ return new HttpHeaders({
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Authorization': 'Bearer ' + this.userAuthenticated.accessToken?.token Authorization:
'Bearer ' + this.userAuthenticated.accessToken?.token,
}); });
} }
private async readAsArrayBuffer(file: File): Promise<ArrayBuffer> { private async readAsArrayBuffer(file: File): Promise<ArrayBuffer> {
const reader = new FileReader(); const reader = new FileReader();
reader.readAsArrayBuffer(file) reader.readAsArrayBuffer(file);
return new Promise<ArrayBuffer>((resolve, reject) => { return new Promise<ArrayBuffer>((resolve, reject) => {
reader.onload = () => { reader.onload = () => {
resolve(reader.result as ArrayBuffer); resolve(reader.result as ArrayBuffer);

View File

@@ -1,8 +1,8 @@
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700;800;900&display=swap'); @import url("https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700;800;900&display=swap");
* { * {
box-sizing: border-box; box-sizing: border-box;
font-family: 'Poppins', sans-serif; font-family: "Poppins", sans-serif;
} }
.popup-background { .popup-background {
@@ -51,7 +51,7 @@
background: #808080; background: #808080;
border-radius: 5px; border-radius: 5px;
position: fixed; position: fixed;
content: ''; content: "";
width: 25px; width: 25px;
height: 5px; height: 5px;
} }
@@ -73,11 +73,9 @@
height: 60px; height: 60px;
} }
@media (min-width:767px) { @media (min-width: 767px) {
.popup { .popup {
width: fit-content; width: fit-content;
max-width: unset; max-width: unset;
} }
} }

View File

@@ -1,11 +1,15 @@
<div #popup <div
class="popup-background" #popup
[@popupState]="popupState" class="popup-background"
(@popupState.done)="animationStop()"> [@popupState]="popupState"
<div class="popup" (@popupState.done)="animationStop()"
appClickedOutside >
(clickOutside)="closePopup()" <div
[ignoreElementList]="ignoreClickOutside"> class="popup"
appClickedOutside
(clickOutside)="closePopup()"
[ignoreElementList]="ignoreClickOutside"
>
<div class="popup-header"> <div class="popup-header">
<div class="popup-close-btn" (click)="closePopup()"></div> <div class="popup-close-btn" (click)="closePopup()"></div>
</div> </div>

View File

@@ -8,9 +8,8 @@ describe('PopupComponent', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ PopupComponent ] declarations: [PopupComponent],
}) }).compileComponents();
.compileComponents();
fixture = TestBed.createComponent(PopupComponent); fixture = TestBed.createComponent(PopupComponent);
component = fixture.componentInstance; component = fixture.componentInstance;

View File

@@ -1,5 +1,21 @@
import { animate, animateChild, group, query, state, style, transition, trigger } from '@angular/animations'; import {
import { Component, ElementRef, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core'; animate,
animateChild,
group,
query,
state,
style,
transition,
trigger,
} from '@angular/animations';
import {
Component,
ElementRef,
EventEmitter,
Input,
Output,
ViewEncapsulation,
} from '@angular/core';
@Component({ @Component({
selector: 'app-popup', selector: 'app-popup',
@@ -7,49 +23,46 @@ import { Component, ElementRef, EventEmitter, Input, Output, ViewEncapsulation }
styleUrls: ['./popup.component.css'], styleUrls: ['./popup.component.css'],
animations: [ animations: [
trigger('popupState', [ trigger('popupState', [
state('hide', style({ state(
'opacity': '0' 'hide',
})), style({
state('show', style({ opacity: '0',
'opacity': '1' }),
})), ),
state(
'show',
style({
opacity: '1',
}),
),
transition( transition(
'* => show', '* => show',
group([ group([
query( query('@*', animateChild(), { optional: true }),
"@*", animate('250ms ease-in'),
animateChild(), ]),
{ optional: true }
),
animate('250ms ease-in')
])
), ),
transition( transition(
'show => hide', 'show => hide',
group([ group([
query( query('@*', animateChild(), { optional: true }),
"@*", animate('250ms ease-out'),
animateChild(), ]),
{ optional: true } ),
), ]),
animate('250ms ease-out') ],
])
)
])
]
}) })
export class PopupComponent { export class PopupComponent {
@Input()
state: boolean = false;
@Input() @Input()
state: boolean = false; ignoreClickOutside!: HTMLDivElement[];
@Input()
ignoreClickOutside!: HTMLDivElement[];
@Output() @Output()
stateChange = new EventEmitter<boolean>(false); stateChange = new EventEmitter<boolean>(false);
constructor() { } constructor() {}
get popupState(): string { get popupState(): string {
return this.state ? 'show' : 'hide'; return this.state ? 'show' : 'hide';
@@ -57,7 +70,7 @@ export class PopupComponent {
animationStop() { animationStop() {
if (!this.state) { if (!this.state) {
this.closePopup() this.closePopup();
this.stateChange.emit(false); this.stateChange.emit(false);
} }
} }
@@ -65,5 +78,4 @@ export class PopupComponent {
closePopup(): void { closePopup(): void {
this.state = false; this.state = false;
} }
} }

View File

@@ -8,9 +8,8 @@ describe('SliderItemComponent', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ SliderItemComponent ] declarations: [SliderItemComponent],
}) }).compileComponents();
.compileComponents();
fixture = TestBed.createComponent(SliderItemComponent); fixture = TestBed.createComponent(SliderItemComponent);
component = fixture.componentInstance; component = fixture.componentInstance;

View File

@@ -1,46 +1,56 @@
import { animate, state, style, transition, trigger } from '@angular/animations'; import {
animate,
state,
style,
transition,
trigger,
} from '@angular/animations';
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
@Component({ @Component({
selector: 'app-slider-item', selector: 'app-slider-item',
templateUrl: './slider-item.component.html', templateUrl: './slider-item.component.html',
styleUrls: ['./slider-item.component.css'], styleUrls: ['./slider-item.component.css'],
animations:[ animations: [
trigger('animateSliderItem',[ trigger('animateSliderItem', [
state('hide', style({ state(
'opacity': '0', 'hide',
'transform': 'translateX(150px)' style({
}), opacity: '0',
{ transform: 'translateX(150px)',
params: { }),
fadeInTime: 600, {
fadeOutTime: 600 params: {
} fadeInTime: 600,
}), fadeOutTime: 600,
state('show', style({ },
'opacity': '1',
'transform': 'translateX(0px)'
}),
{
params: {
fadeOutTime: 600,
fadeInTime: 600
}, },
}), ),
state(
'show',
style({
opacity: '1',
transform: 'translateX(0px)',
}),
{
params: {
fadeOutTime: 600,
fadeInTime: 600,
},
},
),
transition('hide => show', animate(`{{ fadeInTime }}s ease-in`)), transition('hide => show', animate(`{{ fadeInTime }}s ease-in`)),
transition('show => hide', animate(`{{ fadeOutTime }}s ease-out`)) transition('show => hide', animate(`{{ fadeOutTime }}s ease-out`)),
]) ]),
] ],
}) })
export class SliderItemComponent { export class SliderItemComponent {
@Input() @Input()
public state:boolean = false; public state: boolean = false;
constructor() { } constructor() {}
get itemStatus(): string { get itemStatus(): string {
return this.state ? 'show' : 'hide'; return this.state ? 'show' : 'hide';
} }
} }

View File

@@ -1,44 +1,42 @@
import {NgModule} from '@angular/core'; import { NgModule } from '@angular/core';
import {CommonModule} from '@angular/common'; import { CommonModule } from '@angular/common';
import {NgcCookieConsentConfig, NgcCookieConsentModule} from "ngx-cookieconsent"; import {
NgcCookieConsentConfig,
NgcCookieConsentModule,
} from 'ngx-cookieconsent';
const cookieConfig: NgcCookieConsentConfig = { const cookieConfig: NgcCookieConsentConfig = {
"cookie": { cookie: {
"domain": "tinesoft.github.io" domain: 'tinesoft.github.io',
}, },
"position": "bottom-left", position: 'bottom-left',
"theme": "classic", theme: 'classic',
"palette": { palette: {
"popup": { popup: {
"background": "#4e4e4e", background: '#4e4e4e',
"text": "#ffffff", text: '#ffffff',
"link": "#ffffff" link: '#ffffff',
},
button: {
background: '#fa2f22',
text: '#ffffff',
border: 'transparent',
}, },
"button": {
"background": "#fa2f22",
"text": "#ffffff",
"border": "transparent"
}
}, },
"type": "opt-in", type: 'opt-in',
"content": { content: {
"message": "This website uses cookies to ensure you get the best experience on our website.", message:
"dismiss": "Got it!", 'This website uses cookies to ensure you get the best experience on our website.',
"deny": "Refuse cookies", dismiss: 'Got it!',
"link": "", deny: 'Refuse cookies',
"href": "", link: '',
"policy": "Cookie Policy" href: '',
} policy: 'Cookie Policy',
},
}; };
@NgModule({ @NgModule({
declarations: [], declarations: [],
imports: [ imports: [CommonModule, NgcCookieConsentModule.forRoot(cookieConfig)],
CommonModule,
NgcCookieConsentModule.forRoot(cookieConfig)
]
}) })
export class CookieConsentModule { export class CookieConsentModule {}
}

View File

@@ -3,14 +3,14 @@ import { TestBed } from '@angular/core/testing';
import { CookieConsertService } from './cookie-consert.service'; import { CookieConsertService } from './cookie-consert.service';
describe('CookieConsertService', () => { describe('CookieConsertService', () => {
let service: CookieConsertService; let service: CookieConsertService;
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({}); TestBed.configureTestingModule({});
service = TestBed.inject(CookieConsertService); service = TestBed.inject(CookieConsertService);
}); });
it('should be created', () => { it('should be created', () => {
expect(service).toBeTruthy(); expect(service).toBeTruthy();
}); });
}); });

View File

@@ -1,21 +1,20 @@
import {Injectable} from '@angular/core'; import { Injectable } from '@angular/core';
import {BehaviorSubject, Subject, Subscription} from "rxjs"; import { BehaviorSubject, Subject, Subscription } from 'rxjs';
import {CookieService} from "ngx-cookie-service"; import { CookieService } from 'ngx-cookie-service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root',
}) })
export class CookieConsertService { export class CookieConsertService {
private storage: Storage; private storage: Storage;
cookieStatusChangeSubscription!: BehaviorSubject<boolean> cookieStatusChangeSubscription!: BehaviorSubject<boolean>;
constructor(private cookieService: CookieService) { constructor(private cookieService: CookieService) {
this.storage = window.localStorage this.storage = window.localStorage;
this.cookieStatusChangeSubscription = new BehaviorSubject<boolean>( this.cookieStatusChangeSubscription = new BehaviorSubject<boolean>(
this.getCookieConsentStatusFromLocalStorage() this.getCookieConsentStatusFromLocalStorage(),
); );
} }
@@ -48,5 +47,4 @@ export class CookieConsertService {
return status === 'true'; return status === 'true';
} }
} }

View File

@@ -1,12 +1,28 @@
import { DOCUMENT } from '@angular/common'; import { DOCUMENT } from '@angular/common';
import { AfterViewInit, Directive, ElementRef, EventEmitter, Inject, Input, OnDestroy, Output, ViewChild } from '@angular/core'; import {
import { combineLatest, combineLatestWith, filter, fromEvent, merge, Subscription } from 'rxjs'; AfterViewInit,
Directive,
ElementRef,
EventEmitter,
Inject,
Input,
OnDestroy,
Output,
ViewChild,
} from '@angular/core';
import {
combineLatest,
combineLatestWith,
filter,
fromEvent,
merge,
Subscription,
} from 'rxjs';
@Directive({ @Directive({
selector: '[appClickedOutside]' selector: '[appClickedOutside]',
}) })
export class ClickedOutsideDirective implements AfterViewInit, OnDestroy { export class ClickedOutsideDirective implements AfterViewInit, OnDestroy {
@Input() @Input()
ignoreElementList!: HTMLDivElement[]; ignoreElementList!: HTMLDivElement[];
@@ -23,27 +39,27 @@ export class ClickedOutsideDirective implements AfterViewInit, OnDestroy {
constructor( constructor(
private element: ElementRef, private element: ElementRef,
@Inject(DOCUMENT) private document: Document @Inject(DOCUMENT) private document: Document,
) { } ) {}
ngAfterViewInit(): void { ngAfterViewInit(): void {
const clickListener$ = fromEvent(this.document, 'click'); const clickListener$ = fromEvent(this.document, 'click');
this.eventListener = clickListener$.pipe( this.eventListener = clickListener$
filter((click) => { .pipe(
return ( filter((click) => {
( return (
this.isOutside(click.target as HTMLElement) || (this.isOutside(click.target as HTMLElement) ||
this.isInIncludedList(click.target as HTMLElement) this.isInIncludedList(
) && click.target as HTMLElement,
this.notInIgnoredList(click.target as HTMLElement) )) &&
); this.notInIgnoredList(click.target as HTMLElement)
}) );
). subscribe( () => { }),
!this.clickOutsideStopWatching && this.clickOutside.emit(); )
}); .subscribe(() => {
!this.clickOutsideStopWatching && this.clickOutside.emit();
});
} }
ngOnDestroy(): void { ngOnDestroy(): void {
@@ -52,12 +68,14 @@ export class ClickedOutsideDirective implements AfterViewInit, OnDestroy {
private isOutside(elementToCheck: HTMLElement): boolean { private isOutside(elementToCheck: HTMLElement): boolean {
let status = true; let status = true;
if (this.element.nativeElement === elementToCheck || if (
this.element.nativeElement.contains(elementToCheck)) { this.element.nativeElement === elementToCheck ||
this.element.nativeElement.contains(elementToCheck)
) {
status = false; status = false;
} }
console.log('isOutside', status) console.log('isOutside', status);
return status; return status;
} }
@@ -68,26 +86,32 @@ export class ClickedOutsideDirective implements AfterViewInit, OnDestroy {
} }
let validateIsIgnored = (ignoreElement: HTMLDivElement): boolean => { let validateIsIgnored = (ignoreElement: HTMLDivElement): boolean => {
return ignoreElement === elementToCheck return (
|| ignoreElement.contains(elementToCheck) ignoreElement === elementToCheck ||
|| elementToCheck.contains(ignoreElement) ignoreElement.contains(elementToCheck) ||
} elementToCheck.contains(ignoreElement)
);
};
return !this.ignoreElementList.some(validateIsIgnored) return !this.ignoreElementList.some(validateIsIgnored);
} }
private isInIncludedList(elementToCheck: HTMLElement): boolean { private isInIncludedList(elementToCheck: HTMLElement): boolean {
if (!this.includeClickedOutside || this.includeClickedOutside.length === 0) { if (
!this.includeClickedOutside ||
this.includeClickedOutside.length === 0
) {
return false; return false;
} }
let validateIsIncluded = (includedElement: HTMLDivElement): boolean => { let validateIsIncluded = (includedElement: HTMLDivElement): boolean => {
return includedElement === elementToCheck return (
|| includedElement.contains(elementToCheck) includedElement === elementToCheck ||
|| elementToCheck.contains(includedElement) includedElement.contains(elementToCheck) ||
} elementToCheck.contains(includedElement)
);
};
return !this.includeClickedOutside.some(validateIsIncluded) return !this.includeClickedOutside.some(validateIsIncluded);
} }
} }

View File

@@ -1,15 +1,15 @@
/** /**
* This module was automatically generated by `ts-interface-builder` * This module was automatically generated by `ts-interface-builder`
*/ */
import * as t from "ts-interface-checker"; import * as t from 'ts-interface-checker';
// tslint:disable:object-literal-key-quotes // tslint:disable:object-literal-key-quotes
export const HttpError = t.iface([], { export const HttpError = t.iface([], {
"title": "string", title: 'string',
"status": "number", status: 'number',
"details": "string", details: 'string',
"developerMessage": "string", developerMessage: 'string',
"timestamp": "string", timestamp: 'string',
}); });
const exportedTypeSuite: t.ITypeSuite = { const exportedTypeSuite: t.ITypeSuite = {

View File

@@ -1,5 +1,5 @@
import { createCheckers } from "ts-interface-checker"; import { createCheckers } from 'ts-interface-checker';
import HttpError from "./httpError.model-ti"; import HttpError from './httpError.model-ti';
const HttpErrorChecker = createCheckers(HttpError)['HttpError']; const HttpErrorChecker = createCheckers(HttpError)['HttpError'];
export default HttpErrorChecker; export default HttpErrorChecker;

View File

@@ -1,12 +1,12 @@
/** /**
* This module was automatically generated by `ts-interface-builder` * This module was automatically generated by `ts-interface-builder`
*/ */
import * as t from "ts-interface-checker"; import * as t from 'ts-interface-checker';
// tslint:disable:object-literal-key-quotes // tslint:disable:object-literal-key-quotes
export const Token = t.iface([], { export const Token = t.iface([], {
"token": "string", token: 'string',
"expirationDate": t.union("string", "number"), expirationDate: t.union('string', 'number'),
}); });
const exportedTypeSuite: t.ITypeSuite = { const exportedTypeSuite: t.ITypeSuite = {

View File

@@ -1,4 +1,4 @@
export interface Token { export interface Token {
token: string, token: string;
expirationDate: string|number expirationDate: string | number;
} }

View File

@@ -1,6 +1,6 @@
import { createCheckers } from "ts-interface-checker"; import { createCheckers } from 'ts-interface-checker';
import User from "./user.model-ti"; import User from './user.model-ti';
import Token from "../token/token.model-ti"; import Token from '../token/token.model-ti';
const UserChecker = createCheckers(User, Token)['User']; const UserChecker = createCheckers(User, Token)['User'];
export default UserChecker; export default UserChecker;

View File

@@ -1,19 +1,19 @@
/** /**
* This module was automatically generated by `ts-interface-builder` * This module was automatically generated by `ts-interface-builder`
*/ */
import * as t from "ts-interface-checker"; import * as t from 'ts-interface-checker';
// tslint:disable:object-literal-key-quotes // tslint:disable:object-literal-key-quotes
export const User = t.iface([], { export const User = t.iface([], {
"id": t.opt("number"), id: t.opt('number'),
"name": t.opt("string"), name: t.opt('string'),
"email": t.opt("string"), email: t.opt('string'),
"username": "string", username: 'string',
"password": t.opt("string"), password: t.opt('string'),
"profilePictureUrl": t.opt("string"), profilePictureUrl: t.opt('string'),
"accessToken": t.opt("Token"), accessToken: t.opt('Token'),
"refreshToken": t.opt("Token"), refreshToken: t.opt('Token'),
"roles": t.opt(t.array("string")), roles: t.opt(t.array('string')),
}); });
const exportedTypeSuite: t.ITypeSuite = { const exportedTypeSuite: t.ITypeSuite = {

View File

@@ -1,14 +1,14 @@
import { Token } from "../token/token.model"; import { Token } from '../token/token.model';
export interface User { export interface User {
id?: number, id?: number;
name?: string, name?: string;
email?: string, email?: string;
username: string, username: string;
password?: string, password?: string;
profilePictureUrl?: string, profilePictureUrl?: string;
accessToken?: Token, accessToken?: Token;
refreshToken?: Token, refreshToken?: Token;
roles?: Array<string>, roles?: Array<string>;
validateAccessToken?: () => Token | undefined; validateAccessToken?: () => Token | undefined;
}; }

View File

@@ -3,14 +3,14 @@ import { TestBed } from '@angular/core/testing';
import { UpdateService } from './update.service'; import { UpdateService } from './update.service';
describe('UpdateService', () => { describe('UpdateService', () => {
let service: UpdateService; let service: UpdateService;
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({}); TestBed.configureTestingModule({});
service = TestBed.inject(UpdateService); service = TestBed.inject(UpdateService);
}); });
it('should be created', () => { it('should be created', () => {
expect(service).toBeTruthy(); expect(service).toBeTruthy();
}); });
}); });

View File

@@ -1,26 +1,27 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import {SwUpdate} from "@angular/service-worker"; import { SwUpdate } from '@angular/service-worker';
import {interval} from "rxjs"; import { interval } from 'rxjs';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root',
}) })
export class UpdateService { export class UpdateService {
constructor(private swUpdate: SwUpdate) { constructor(private swUpdate: SwUpdate) {
if (swUpdate.isEnabled) { if (swUpdate.isEnabled) {
interval(6 * 60 * 60).subscribe(() => swUpdate.checkForUpdate() interval(6 * 60 * 60).subscribe(() =>
.then(() => console.log('checking for updates'))); swUpdate
.checkForUpdate()
.then(() => console.log('checking for updates')),
);
} }
} }
public checkForUpdates(): void { public checkForUpdates(): void {
this.swUpdate.available.subscribe(event => this.promptUser()); this.swUpdate.available.subscribe((event) => this.promptUser());
} }
private promptUser(): void { private promptUser(): void {
console.log('updating to new version'); console.log('updating to new version');
this.swUpdate.activateUpdate().then(() => document.location.reload()); this.swUpdate.activateUpdate().then(() => document.location.reload());
} }
} }

View File

@@ -6,7 +6,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule } from '@angular/common/http';
import { PopupComponent } from './components/popup/popup.component'; import { PopupComponent } from './components/popup/popup.component';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import {CookieConsentModule} from "./cookie-consent/cookie-consent.module"; import { CookieConsentModule } from './cookie-consent/cookie-consent.module';
@NgModule({ @NgModule({
declarations: [ declarations: [
@@ -19,12 +19,8 @@ import {CookieConsentModule} from "./cookie-consent/cookie-consent.module";
HttpClientModule, HttpClientModule,
BrowserAnimationsModule, BrowserAnimationsModule,
FontAwesomeModule, FontAwesomeModule,
CookieConsentModule CookieConsentModule,
], ],
exports: [ exports: [ClickedOutsideDirective, SliderItemComponent, PopupComponent],
ClickedOutsideDirective,
SliderItemComponent,
PopupComponent
]
}) })
export class SharedModule { } export class SharedModule {}

View File

@@ -1,4 +1,4 @@
import {AbstractControl} from "@angular/forms"; import { AbstractControl } from '@angular/forms';
export function ValidateEmailValidator(control: AbstractControl) { export function ValidateEmailValidator(control: AbstractControl) {
const email = control.value; const email = control.value;

View File

@@ -1,4 +1,4 @@
import {AbstractControl} from "@angular/forms"; import { AbstractControl } from '@angular/forms';
export function ValidateNotEmptyValidator(control: AbstractControl) { export function ValidateNotEmptyValidator(control: AbstractControl) {
const value = control.value; const value = control.value;

View File

@@ -1,8 +1,9 @@
import {AbstractControl} from "@angular/forms"; import { AbstractControl } from '@angular/forms';
export function ValidatePasswordValidator(control: AbstractControl) { export function ValidatePasswordValidator(control: AbstractControl) {
var password = control.value; var password = control.value;
var passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/g; var passwordRegex =
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/g;
var passwordValid = passwordRegex.test(password); var passwordValid = passwordRegex.test(password);
if (!passwordValid) { if (!passwordValid) {
return { invalidPassword: true }; return { invalidPassword: true };

View File

@@ -1,7 +1,7 @@
(function (window) { (function (window) {
window['env'] = window['env'] || {}; window["env"] = window["env"] || {};
// Environment variables // Environment variables
window['env']['BACKEND_URL'] = '${BACKEND_URL}'; window["env"]["BACKEND_URL"] = "${BACKEND_URL}";
window['env']['BACKEND_OAUTH_URL'] = '${BACKEND_OAUTH_URL}'; window["env"]["BACKEND_OAUTH_URL"] = "${BACKEND_OAUTH_URL}";
})(this); })(this);

View File

@@ -1,5 +1,5 @@
export const environment = { export const environment = {
production: true, production: true,
backendPath: (<any>window)['env']['BACKEND_URL'], backendPath: (<any>window)['env']['BACKEND_URL'],
backendOAuthPath: (<any>window)['env']['BACKEND_OAUTH_URL'] backendOAuthPath: (<any>window)['env']['BACKEND_OAUTH_URL'],
}; };

View File

@@ -5,7 +5,7 @@
export const environment = { export const environment = {
production: false, production: false,
backendPath: 'http://localhost:8070', backendPath: 'http://localhost:8070',
backendOAuthPath: 'http://localhost:8070' backendOAuthPath: 'http://localhost:8070',
}; };
/* /*

View File

@@ -1,71 +1,115 @@
@font-face { @font-face {
font-family: 'Montserrat'; font-family: "Montserrat";
font-style: italic; font-style: italic;
font-weight: 100; font-weight: 100;
src: url(assets/fonts/Montserrat/Montserrat-italic-100.woff) format('woff'), url(assets/fonts/Montserrat/Montserrat-italic-100.woff2) format('woff2'), url(assets/fonts/Montserrat/Montserrat-italic-100.ttf) format('truetype'); src:
url(assets/fonts/Montserrat/Montserrat-italic-100.woff) format("woff"),
url(assets/fonts/Montserrat/Montserrat-italic-100.woff2) format("woff2"),
url(assets/fonts/Montserrat/Montserrat-italic-100.ttf)
format("truetype");
} }
@font-face { @font-face {
font-family: 'Montserrat'; font-family: "Montserrat";
font-style: italic; font-style: italic;
font-weight: 300; font-weight: 300;
src: url(assets/fonts/Montserrat/Montserrat-italic-300.woff) format('woff'), url(assets/fonts/Montserrat/Montserrat-italic-300.woff2) format('woff2'), url(assets/fonts/Montserrat/Montserrat-italic-300.ttf) format('truetype'); src:
url(assets/fonts/Montserrat/Montserrat-italic-300.woff) format("woff"),
url(assets/fonts/Montserrat/Montserrat-italic-300.woff2) format("woff2"),
url(assets/fonts/Montserrat/Montserrat-italic-300.ttf)
format("truetype");
} }
@font-face { @font-face {
font-family: 'Montserrat'; font-family: "Montserrat";
font-style: italic; font-style: italic;
font-weight: 400; font-weight: 400;
src: url(assets/fonts/Montserrat/Montserrat-italic-400.woff) format('woff'), url(assets/fonts/Montserrat/Montserrat-italic-400.woff2) format('woff2'), url(assets/fonts/Montserrat/Montserrat-italic-400.ttf) format('truetype'); src:
url(assets/fonts/Montserrat/Montserrat-italic-400.woff) format("woff"),
url(assets/fonts/Montserrat/Montserrat-italic-400.woff2) format("woff2"),
url(assets/fonts/Montserrat/Montserrat-italic-400.ttf)
format("truetype");
} }
@font-face { @font-face {
font-family: 'Montserrat'; font-family: "Montserrat";
font-style: italic; font-style: italic;
font-weight: 700; font-weight: 700;
src: url(assets/fonts/Montserrat/Montserrat-italic-700.woff) format('woff'), url(assets/fonts/Montserrat/Montserrat-italic-700.woff2) format('woff2'), url(assets/fonts/Montserrat/Montserrat-italic-700.ttf) format('truetype'); src:
url(assets/fonts/Montserrat/Montserrat-italic-700.woff) format("woff"),
url(assets/fonts/Montserrat/Montserrat-italic-700.woff2) format("woff2"),
url(assets/fonts/Montserrat/Montserrat-italic-700.ttf)
format("truetype");
} }
@font-face { @font-face {
font-family: 'Montserrat'; font-family: "Montserrat";
font-style: italic; font-style: italic;
font-weight: 900; font-weight: 900;
src: url(assets/fonts/Montserrat/Montserrat-italic-900.woff) format('woff'), url(assets/fonts/Montserrat/Montserrat-italic-900.woff2) format('woff2'), url(assets/fonts/Montserrat/Montserrat-italic-900.ttf) format('truetype'); src:
url(assets/fonts/Montserrat/Montserrat-italic-900.woff) format("woff"),
url(assets/fonts/Montserrat/Montserrat-italic-900.woff2) format("woff2"),
url(assets/fonts/Montserrat/Montserrat-italic-900.ttf)
format("truetype");
} }
@font-face { @font-face {
font-family: 'Montserrat'; font-family: "Montserrat";
font-style: normal; font-style: normal;
font-weight: 100; font-weight: 100;
src: url(assets/fonts/Montserrat/Montserrat-normal-100.woff) format('woff'), url(assets/fonts/Montserrat/Montserrat-normal-100.woff2) format('woff2'), url(assets/fonts/Montserrat/Montserrat-normal-100.ttf) format('truetype'); src:
url(assets/fonts/Montserrat/Montserrat-normal-100.woff) format("woff"),
url(assets/fonts/Montserrat/Montserrat-normal-100.woff2) format("woff2"),
url(assets/fonts/Montserrat/Montserrat-normal-100.ttf)
format("truetype");
} }
@font-face { @font-face {
font-family: 'Montserrat'; font-family: "Montserrat";
font-style: normal; font-style: normal;
font-weight: 300; font-weight: 300;
src: url(assets/fonts/Montserrat/Montserrat-normal-300.woff) format('woff'), url(assets/fonts/Montserrat/Montserrat-normal-300.woff2) format('woff2'), url(assets/fonts/Montserrat/Montserrat-normal-300.ttf) format('truetype'); src:
url(assets/fonts/Montserrat/Montserrat-normal-300.woff) format("woff"),
url(assets/fonts/Montserrat/Montserrat-normal-300.woff2) format("woff2"),
url(assets/fonts/Montserrat/Montserrat-normal-300.ttf)
format("truetype");
} }
@font-face { @font-face {
font-family: 'Montserrat'; font-family: "Montserrat";
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
src: url(assets/fonts/Montserrat/Montserrat-normal-400.eot); src: url(assets/fonts/Montserrat/Montserrat-normal-400.eot);
src: local('Montserrat'), url(assets/fonts/Montserrat/Montserrat-normal-400.woff) format('woff'), url(assets/fonts/Montserrat/Montserrat-normal-400.woff2) format('woff2'), url(assets/fonts/Montserrat/Montserrat-normal-400.svg#Montserrat) format('svg'), url(assets/fonts/Montserrat/Montserrat-normal-400.eot?#iefix) format('embedded-opentype'), url(assets/fonts/Montserrat/Montserrat-normal-400.ttf) format('truetype'); src:
local("Montserrat"),
url(assets/fonts/Montserrat/Montserrat-normal-400.woff) format("woff"),
url(assets/fonts/Montserrat/Montserrat-normal-400.woff2) format("woff2"),
url(assets/fonts/Montserrat/Montserrat-normal-400.svg#Montserrat)
format("svg"),
url(assets/fonts/Montserrat/Montserrat-normal-400.eot?#iefix)
format("embedded-opentype"),
url(assets/fonts/Montserrat/Montserrat-normal-400.ttf)
format("truetype");
} }
@font-face { @font-face {
font-family: 'Montserrat'; font-family: "Montserrat";
font-style: normal; font-style: normal;
font-weight: 700; font-weight: 700;
src: url(assets/fonts/Montserrat/Montserrat-normal-700.woff) format('woff'), url(assets/fonts/Montserrat/Montserrat-normal-700.woff2) format('woff2'), url(assets/fonts/Montserrat/Montserrat-normal-700.ttf) format('truetype'); src:
url(assets/fonts/Montserrat/Montserrat-normal-700.woff) format("woff"),
url(assets/fonts/Montserrat/Montserrat-normal-700.woff2) format("woff2"),
url(assets/fonts/Montserrat/Montserrat-normal-700.ttf)
format("truetype");
} }
@font-face { @font-face {
font-family: 'Montserrat'; font-family: "Montserrat";
font-style: normal; font-style: normal;
font-weight: 900; font-weight: 900;
src: url(assets/fonts/Montserrat/Montserrat-normal-900.woff) format('woff'), url(assets/fonts/Montserrat/Montserrat-normal-900.woff2) format('woff2'), url(assets/fonts/Montserrat/Montserrat-normal-900.ttf) format('truetype'); src:
url(assets/fonts/Montserrat/Montserrat-normal-900.woff) format("woff"),
url(assets/fonts/Montserrat/Montserrat-normal-900.woff2) format("woff2"),
url(assets/fonts/Montserrat/Montserrat-normal-900.ttf)
format("truetype");
} }

View File

@@ -1,31 +1,39 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head>
<meta charset="utf-8" />
<title>Hideyoshi Portfolio</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="icon"
type="image/x-icon"
href="./assets/img/logohideyoshi-red.png"
/>
<head> <meta name="viewport" content="width=device-width,initial-scale=1" />
<meta charset="utf-8"> <meta name="mobile-web-app-capable" content="yes" />
<title>Hideyoshi Portfolio</title> <meta name="apple-mobile-web-app-capable" content="yes" />
<base href="/"> <meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="mobile-web-app-capable" content="yes" />
<link rel="icon" type="image/x-icon" href="./assets/img/logohideyoshi-red.png"> <meta name="apple-mobile-web-app-status-bar-style" content="black" />
<meta name="apple-mobile-web-app-title" content="Hideyoshi" />
<link rel="manifest" href="manifest.webmanifest" />
<link
rel="preconnect"
href="https://hideyoshi-portfolio-dev.s3.amazonaws.com"
/>
<meta name="viewport" content="width=device-width,initial-scale=1"> <!-- Loading environment variables -->
<meta name="mobile-web-app-capable" content="yes"> <script src="./assets/env.js"></script>
<meta name="apple-mobile-web-app-capable" content="yes"> </head>
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Hideyoshi">
<link rel="manifest" href="manifest.webmanifest"> <body>
<link rel="preconnect" href="https://hideyoshi-portfolio-dev.s3.amazonaws.com"> <app-root></app-root>
<noscript
<!-- Loading environment variables --> >Please enable JavaScript to continue using this
<script src="./assets/env.js"></script> application.</noscript
</head> >
</body>
<body>
<app-root></app-root>
<noscript>Please enable JavaScript to continue using this application.</noscript>
</body>
</html> </html>

View File

@@ -8,5 +8,6 @@ if (environment.production) {
enableProdMode(); enableProdMode();
} }
platformBrowserDynamic().bootstrapModule(AppModule) platformBrowserDynamic()
.catch(err => console.error(err)); .bootstrapModule(AppModule)
.catch((err) => console.error(err));

View File

@@ -1,17 +1,17 @@
{ {
"name": "Hideyoshi Portfolio", "name": "Hideyoshi Portfolio",
"short_name": "Hideyoshi", "short_name": "Hideyoshi",
"theme_color": "#2e2e2e", "theme_color": "#2e2e2e",
"background_color": "#2e2e2e", "background_color": "#2e2e2e",
"display": "standalone", "display": "standalone",
"orientation": "portrait", "orientation": "portrait",
"scope": "./", "scope": "./",
"start_url": "./", "start_url": "./",
"icons": [ "icons": [
{ {
"src": "assets/icons/icon-512x512.png", "src": "assets/icons/icon-512x512.png",
"sizes": "512x512", "sizes": "512x512",
"type": "image/png" "type": "image/png"
} }
] ]
} }

View File

@@ -45,8 +45,7 @@
/*************************************************************************************************** /***************************************************************************************************
* Zone JS is required by default for Angular itself. * Zone JS is required by default for Angular itself.
*/ */
import 'zone.js'; // Included with Angular CLI. import 'zone.js'; // Included with Angular CLI.
/*************************************************************************************************** /***************************************************************************************************
* APPLICATION IMPORTS * APPLICATION IMPORTS

View File

@@ -1,11 +1,16 @@
/* 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";
* { * {
padding: 0 auto; padding: 0 auto;
margin: 0 auto; margin: 0 auto;
} }
h1, h2, h3, h4, h5, h6 { h1,
font-family: 'Montserrat', sans-serif; h2,
h3,
h4,
h5,
h6 {
font-family: "Montserrat", sans-serif;
} }

View File

@@ -4,7 +4,7 @@ import 'zone.js/testing';
import { getTestBed } from '@angular/core/testing'; import { getTestBed } from '@angular/core/testing';
import { import {
BrowserDynamicTestingModule, BrowserDynamicTestingModule,
platformBrowserDynamicTesting platformBrowserDynamicTesting,
} from '@angular/platform-browser-dynamic/testing'; } from '@angular/platform-browser-dynamic/testing';
// First, initialize the Angular testing environment. // First, initialize the Angular testing environment.

View File

@@ -1,15 +1,10 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */ /* To learn more about this file see: https://angular.io/config/tsconfig. */
{ {
"extends": "./tsconfig.json", "extends": "./tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "./out-tsc/app", "outDir": "./out-tsc/app",
"types": ["node"] "types": ["node"]
}, },
"files": [ "files": ["src/main.ts", "src/polyfills.ts"],
"src/main.ts", "include": ["src/**/*.d.ts"]
"src/polyfills.ts"
],
"include": [
"src/**/*.d.ts"
]
} }

View File

@@ -1,33 +1,30 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */ /* To learn more about this file see: https://angular.io/config/tsconfig. */
{ {
"compileOnSave": false, "compileOnSave": false,
"compilerOptions": { "compilerOptions": {
"baseUrl": "./", "baseUrl": "./",
"outDir": "./dist/out-tsc", "outDir": "./dist/out-tsc",
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"strict": true, "strict": true,
"noImplicitOverride": true, "noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true, "noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true, "noImplicitReturns": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"sourceMap": true, "sourceMap": true,
"declaration": false, "declaration": false,
"downlevelIteration": true, "downlevelIteration": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"moduleResolution": "node", "moduleResolution": "node",
"importHelpers": true, "importHelpers": true,
"target": "ES2022", "target": "ES2022",
"module": "es2020", "module": "es2020",
"lib": [ "lib": ["es2020", "dom"],
"es2020", "useDefineForClassFields": false
"dom" },
], "angularCompilerOptions": {
"useDefineForClassFields": false "enableI18nLegacyMessageIdFormat": false,
}, "strictInjectionParameters": true,
"angularCompilerOptions": { "strictInputAccessModifiers": true,
"enableI18nLegacyMessageIdFormat": false, "strictTemplates": true
"strictInjectionParameters": true, }
"strictInputAccessModifiers": true,
"strictTemplates": true
}
} }

View File

@@ -1,18 +1,10 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */ /* To learn more about this file see: https://angular.io/config/tsconfig. */
{ {
"extends": "./tsconfig.json", "extends": "./tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "./out-tsc/spec", "outDir": "./out-tsc/spec",
"types": [ "types": ["jasmine"]
"jasmine" },
] "files": ["src/test.ts", "src/polyfills.ts"],
}, "include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
"files": [
"src/test.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
} }