Merge pull request #36 from HideyoshiNakazone/formatting-project

Running Prettier in Project
This commit is contained in:
2023-10-14 19:19:10 -03:00
committed by GitHub
100 changed files with 18174 additions and 17212 deletions

View File

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

View File

@@ -1,46 +1,42 @@
name: ci
on:
push:
branches:
- 'main'
push:
branches:
- "main"
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:
matrix:
node-version: [18.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
docker:
needs: [build]
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
steps:
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
docker:
needs: [build]
runs-on: ubuntu-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
- 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",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"allowedCommonJsDependencies": [
"ts-interface-checker"
],
"allowedCommonJsDependencies": ["ts-interface-checker"],
"assets": [
"src/assets",
"src/manifest.webmanifest",
@@ -104,28 +102,21 @@
"src/manifest.webmanifest",
"src/manifest.webmanifest"
],
"styles": [
"src/styles.css"
],
"styles": ["src/styles.css"],
"scripts": []
}
},
"lint": {
"builder": "@angular-eslint/builder:lint",
"options": {
"lintFilePatterns": [
"src/**/*.ts",
"src/**/*.html"
]
"lintFilePatterns": ["src/**/*.ts", "src/**/*.html"]
}
}
}
}
},
"cli": {
"schematicCollections": [
"@angular-eslint/schematics"
],
"schematicCollections": ["@angular-eslint/schematics"],
"analytics": false
},
"schematics": {

View File

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

View File

@@ -1,30 +1,30 @@
{
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
"index": "/index.html",
"assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html",
"/manifest.webmanifest",
"/*.css",
"/*.js"
]
}
},
{
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**",
"/*.(svg|cur|jpg|jpeg|png|apng|webp|avif|gif|otf|ttf|woff|woff2)"
]
}
}
]
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
"index": "/index.html",
"assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html",
"/manifest.webmanifest",
"/*.css",
"/*.js"
]
}
},
{
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**",
"/*.(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",
"version": "0.0.0",
"scripts": {
"start": "node ./server.js",
"build": "ng build",
"serve": "ng serve",
"serve:prod": "ng serve --configuration=production",
"build:prod": "ng build --configuration=production"
},
"proxy": {
"/callback": {
"target": "http://localhost:8070"
"name": "frontend-hideyoshi.com",
"version": "0.0.0",
"scripts": {
"start": "node ./server.js",
"build": "ng build",
"serve": "ng serve",
"serve:prod": "ng serve --configuration=production",
"build:prod": "ng build --configuration=production"
},
"proxy": {
"/callback": {
"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 cors = require('cors');
const path = require('path');
const express = require("express");
const cors = require("cors");
const path = require("path");
const PKG_NAME = "frontend-hideyoshi.com";
@@ -9,7 +9,7 @@ app.use(cors());
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`));
});

View File

@@ -7,27 +7,22 @@ import { CallbackComponent } from './header/header-popup/callback/callback.compo
const routes: Routes = [
{
path: '',
redirectTo: '/home',
pathMatch: 'full'
redirectTo: '/home',
pathMatch: 'full',
},
{
path: 'home',
component: HomeComponent
component: HomeComponent,
},
{
path: 'callback',
component: CallbackComponent
}
]
component: CallbackComponent,
},
];
@NgModule({
declarations: [],
imports: [
CommonModule,
RouterModule.forRoot(routes)
],
exports: [
RouterModule
]
imports: [CommonModule, 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 { ServiceWorkerModule } from '@angular/service-worker';
@NgModule({
declarations: [],
imports: [
CommonModule,
ServiceWorkerModule.register('ngsw-worker.js', {
enabled: environment.production,
registrationStrategy: 'registerWhenStable:30000'
})
registrationStrategy: 'registerWhenStable:30000',
}),
],
exports: [
ServiceWorkerModule
]
exports: [ServiceWorkerModule],
})
export class AppServiceWorkerModule { }
export class AppServiceWorkerModule {}

View File

@@ -4,9 +4,7 @@ import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [
AppComponent
],
declarations: [AppComponent],
}).compileComponents();
});
@@ -26,6 +24,8 @@ describe('AppComponent', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
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 {UpdateService} from "./shared/service-worker/update.service";
import { UpdateService } from './shared/service-worker/update.service';
import {
NgcCookieConsentService,
NgcInitializationErrorEvent,
NgcInitializingEvent,
NgcNoCookieLawEvent, NgcStatusChangeEvent
} from "ngx-cookieconsent";
import {Subscription} from "rxjs";
import {CookieConsertService} from "./shared/cookie-consent/cookie-consert.service";
NgcNoCookieLawEvent,
NgcStatusChangeEvent,
} from 'ngx-cookieconsent';
import { Subscription } from 'rxjs';
import { CookieConsertService } from './shared/cookie-consent/cookie-consert.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
title = 'frontend-hideyoshi.com';
cookieStatusChangeSubscription!: Subscription;
@@ -25,28 +25,30 @@ export class AppComponent implements OnInit {
private authService: AuthService,
private ccService: NgcCookieConsentService,
private cookieConsentService: CookieConsertService,
private serviceWorker: UpdateService) {
private serviceWorker: UpdateService,
) {
this.serviceWorker.checkForUpdates();
}
ngOnInit(): void {
this.authService.autoLogin();
let cookieConsentStatus = this.cookieConsentService.getCookieConsentStatusFromLocalStorage();
let cookieConsentStatus =
this.cookieConsentService.getCookieConsentStatusFromLocalStorage();
if (cookieConsentStatus) {
this.ccService.destroy();
}
this.cookieStatusChangeSubscription = this.ccService.statusChange$.subscribe(
(event: NgcStatusChangeEvent) => {
if (event.status === 'allow') {
this.cookieConsentService.consent();
} else if (event.status === 'deny') {
this.cookieConsentService.decline();
}
}
);
this.cookieStatusChangeSubscription =
this.ccService.statusChange$.subscribe(
(event: NgcStatusChangeEvent) => {
if (event.status === 'allow') {
this.cookieConsentService.consent();
} 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 { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';
import {FooterComponent} from "./footer/footer.component";
import { FooterComponent } from './footer/footer.component';
@NgModule({
declarations: [
AppComponent,
HomeComponent,
FooterComponent
],
declarations: [AppComponent, HomeComponent, FooterComponent],
imports: [
BrowserModule,
HeaderModule,
@@ -27,10 +23,10 @@ import {FooterComponent} from "./footer/footer.component";
FontAwesomeModule,
ServiceWorkerModule.register('ngsw-worker.js', {
enabled: environment.production,
registrationStrategy: 'registerWhenStable:30000'
})
registrationStrategy: 'registerWhenStable:30000',
}),
],
providers: [],
bootstrap: [AppComponent]
bootstrap: [AppComponent],
})
export class AppModule { }
export class AppModule {}

View File

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

View File

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

View File

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

View File

@@ -2,19 +2,19 @@
width: fit-content;
border-radius: 8px;
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-shadow: 1px 1px 4px 1px rgba(0, 0, 0, 0.2);
}
.dropdown:before {
content: '';
content: "";
width: 15px;
height: 15px;
position: absolute;
background-color: #ffffff;
border-top: 1px solid rgba(46, 46, 46, .3);
border-left: 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, 0.3);
transform: translateX(120px) translateY(-50%) rotate(45deg);
}
.info {
@@ -40,7 +40,7 @@
}
.dropdown-item {
display: flex;
display: flex;
align-items: center;
padding: 8px 30px;
border-top: 1px solid rgba(0, 0, 0, 0.05);
@@ -59,14 +59,14 @@
.dropdown-item:hover .icon-box fa-icon {
opacity: 1;
color: #f44336;
transition: .5s;
transition: 0.5s;
}
.dropdown-item p {
color: #555555;
font-family: 'Montserrat', sans-serif;
font-family: "Montserrat", sans-serif;
font-weight: 400;
text-decoration: none;
padding-left: 10px;
margin: 0;
}
}

View File

@@ -6,9 +6,12 @@
[ignoreElementList]="ignoreClickOutside"
[@dropdownState]="dropDownState"
(@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">
<h3>{{ this.user ? this.user.username : 'User Account' }}</h3>
<h3>{{ this.user ? this.user.username : "User Account" }}</h3>
</div>
<div #management>
<ul class="user-management" *ngIf="!this.user">
@@ -34,13 +37,19 @@
</li>
<li class="dropdown-item" (click)="onHelpClicked()">
<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>
<p>Help</p>
</li>
<li class="dropdown-item" (click)="onLogout()">
<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>
<p>Logout</p>
</li>

View File

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

View File

@@ -1,12 +1,32 @@
import { animate, 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 {
animate,
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 { AuthService } from 'src/app/shared/auth/auth.service';
import {User} from "../../shared/model/user/user.model";
import UserChecker from "../../shared/model/user/user.checker";
import {HelpComponent} from "../header-popup/help/help.component";
import {MyProfileComponent} from "../header-popup/my-profile/my-profile.component";
import { User } from '../../shared/model/user/user.model';
import UserChecker from '../../shared/model/user/user.checker';
import { HelpComponent } from '../header-popup/help/help.component';
import { MyProfileComponent } from '../header-popup/my-profile/my-profile.component';
@Component({
selector: 'app-header-dropdown',
@@ -14,19 +34,24 @@ import {MyProfileComponent} from "../header-popup/my-profile/my-profile.componen
styleUrls: ['./header-dropdown.component.css'],
animations: [
trigger('dropdownState', [
state('hide', style({
'opacity': '0'
})),
state('show', style({
'opacity': '1'
})),
state(
'hide',
style({
opacity: '0',
}),
),
state(
'show',
style({
opacity: '1',
}),
),
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 {
userIcon = faUser;
editIcon = faEdit;
@@ -40,38 +65,41 @@ export class HeaderDropdownComponent implements OnInit, OnDestroy {
private userSubscription!: Subscription;
@Input()
state: boolean = false;
state: boolean = false;
@Input()
ignoreClickOutside!: HTMLDivElement[];
ignoreClickOutside!: HTMLDivElement[];
@Output()
clickOutside = new EventEmitter();
clickOutside = new EventEmitter();
@Output()
loginPopupState: EventEmitter<boolean> = new EventEmitter();
loginPopupState: EventEmitter<boolean> = new EventEmitter();
@Output()
signupPopupState: EventEmitter<boolean> = new EventEmitter();
signupPopupState: EventEmitter<boolean> = new EventEmitter();
@Output()
helpPopupState: EventEmitter<boolean> = new EventEmitter();
helpPopupState: EventEmitter<boolean> = new EventEmitter();
@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 {
this.userSubscription = this.authService.authSubject.subscribe(
res => {
(res) => {
if (res && UserChecker.test(res)) {
this.user = <User>res;
} else {
this.user = null;
}
}
)
},
);
}
ngOnDestroy(): void {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,21 +3,20 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HelpComponent } from './help.component';
describe('HelpComponent', () => {
let component: HelpComponent;
let fixture: ComponentFixture<HelpComponent>;
let component: HelpComponent;
let fixture: ComponentFixture<HelpComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ HelpComponent ]
})
.compileComponents();
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [HelpComponent],
}).compileComponents();
fixture = TestBed.createComponent(HelpComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
fixture = TestBed.createComponent(HelpComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should create', () => {
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({
selector: 'app-help',
templateUrl: './help.component.html',
styleUrls: ['./help.component.css']
styleUrls: ['./help.component.css'],
})
export class HelpComponent {
@Input()
state: boolean = false;
@Input()
state: boolean = false;
@Input()
ignoreClickOutside!: HTMLDivElement[];
ignoreClickOutside!: HTMLDivElement[];
@Output()
stateChange = new EventEmitter<boolean>();
stateChange = new EventEmitter<boolean>();
constructor() { }
constructor() {}
onStateChange(state: boolean) {
this.stateChange.emit(state);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,4 +6,4 @@
top: 10vh;
right: 0;
overflow: hidden !important;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,9 +1,18 @@
<div class="links-container">
<div class="nav-links">
<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">
{{nav.page}}
{{ nav.page }}
</a>
</li>
</ul>
@@ -11,18 +20,32 @@
</div>
<div class="profile-container">
<div class="profile"
[@animateSliderItem]="{ value: itemStatus, params: { fadeInTime: .6 + (navLink.length+1)/20 , fadeOutTime: .6 - (navLink.length+1)/10 } }"
#profile>
<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
class="profile"
[@animateSliderItem]="{
value: itemStatus,
params: {
fadeInTime: 0.6 + (navLink.length + 1) / 20,
fadeOutTime: 0.6 - (navLink.length + 1) / 10
}
}"
#profile
>
<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>

View File

@@ -8,9 +8,8 @@ describe('NavSliderComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ NavSliderComponent ]
})
.compileComponents();
declarations: [NavSliderComponent],
}).compileComponents();
fixture = TestBed.createComponent(NavSliderComponent);
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 { SliderItemComponent } from 'src/app/shared/components/slider-item/slider-item.component';
import UserChecker from "../../../shared/model/user/user.checker";
import {User} from "../../../shared/model/user/user.model";
import {AuthService} from "../../../shared/auth/auth.service";
import {Subscription} from "rxjs";
import UserChecker from '../../../shared/model/user/user.checker';
import { User } from '../../../shared/model/user/user.model';
import { AuthService } from '../../../shared/auth/auth.service';
import { Subscription } from 'rxjs';
@Component({
selector: 'app-nav-slider',
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;
navLink = [
{ page: "Home", link: "/home" },
{ page: "Work", link: "/home" },
{ page: "Contact", link: "/home" },
{ page: "About", link: "/home" }
]
{ page: 'Home', link: '/home' },
{ page: 'Work', link: '/home' },
{ page: 'Contact', link: '/home' },
{ page: 'About', link: '/home' },
];
loggedUser!: User | null;
private userSubscription!: Subscription;
@Output()
profileButtonClicked = new EventEmitter();
profileButtonClicked = new EventEmitter();
constructor(private authService: AuthService) {
super();
@@ -35,14 +43,14 @@ export class NavSliderComponent extends SliderItemComponent implements OnInit, O
ngOnInit(): void {
this.userSubscription = this.authService.authSubject.subscribe(
res => {
(res) => {
if (res && UserChecker.test(res)) {
this.loggedUser = <User>res;
} else {
this.loggedUser = null;
}
}
)
},
);
}
ngOnDestroy(): void {
@@ -52,5 +60,4 @@ export class NavSliderComponent extends SliderItemComponent implements OnInit, O
onProfileButtonClicked() {
this.profileButtonClicked.emit();
}
}

View File

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

View File

@@ -1,11 +1,23 @@
<div class="user-container">
<div class="user-options">
<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()">
{{options.name}}
{{ options.name }}
</a>
</li>
</ul>
</div>
</div>
</div>

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
<div class="main" #header>
<div class="logo">
<a routerLink="">
<img src="assets/img/logohideyoshi-white.png" alt="">
<img src="assets/img/logohideyoshi-white.png" alt="" />
</a>
</div>
<div class="nav-links">
@@ -15,14 +15,25 @@
</div>
<div class="profile" #profileDropdown>
<div class="profile-btn" (click)="toogleProfileDropdown()" #profileBtn>
<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
class="profile-btn"
(click)="toogleProfileDropdown()"
#profileBtn
>
<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>
<app-header-dropdown
@@ -33,36 +44,43 @@
(loginPopupState)="loginPopupStateChange($event)"
(signupPopupState)="signupPopupStateChange($event)"
(myProfilePopupState)="myProfilePopupStateChange($event)"
(helpPopupState)="helpPopupStateChange($event)">
(helpPopupState)="helpPopupStateChange($event)"
>
</app-header-dropdown>
</div>
<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 #nav>
<app-header-slider
[(state)]="navSliderStatus"
[clickOutsideStopWatching]="userSliderStatus"
[ignoreClickOutside]="[header, user]">
[(state)]="navSliderStatus"
[clickOutsideStopWatching]="userSliderStatus"
[ignoreClickOutside]="[header, user]"
>
<app-nav-slider
[state]="navSliderStatus"
(profileButtonClicked)="profileButtonClicked()">
(profileButtonClicked)="profileButtonClicked()"
>
</app-nav-slider>
</app-header-slider>
</div>
<div #user>
<app-header-slider
[(state)]="userSliderStatus"
[ignoreClickOutside]="[header, nav]">
[(state)]="userSliderStatus"
[ignoreClickOutside]="[header, nav]"
>
<app-user-slider
[state]="userSliderStatus"
(loginPopupState)="loginPopupStateChange($event)"
(signupPopupState)="signupPopupStateChange($event)"
(myProfilePopupState)="myProfilePopupStateChange($event)"
(helpPopupState)="helpPopupStateChange($event)">
(helpPopupState)="helpPopupStateChange($event)"
>
</app-user-slider>
</app-header-slider>
</div>

View File

@@ -8,9 +8,8 @@ describe('HeaderComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ HeaderComponent ]
})
.compileComponents();
declarations: [HeaderComponent],
}).compileComponents();
fixture = TestBed.createComponent(HeaderComponent);
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 { LoginComponent } from './header-popup/login/login.component';
import { SignupComponent } from './header-popup/signup/signup.component';
import {AuthService} from "../shared/auth/auth.service";
import UserChecker from "../shared/model/user/user.checker";
import {User} from "../shared/model/user/user.model";
import {Subscription} from "rxjs";
import {HelpComponent} from "./header-popup/help/help.component";
import {MyProfileComponent} from "./header-popup/my-profile/my-profile.component";
import { AuthService } from '../shared/auth/auth.service';
import UserChecker from '../shared/model/user/user.checker';
import { User } from '../shared/model/user/user.model';
import { Subscription } from 'rxjs';
import { HelpComponent } from './header-popup/help/help.component';
import { MyProfileComponent } from './header-popup/my-profile/my-profile.component';
@Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.css']
styleUrls: ['./header.component.css'],
})
export class HeaderComponent implements OnInit, OnDestroy {
userIcon = faUser;
profileDropdownState: boolean = false;
@@ -26,13 +33,13 @@ export class HeaderComponent implements OnInit, OnDestroy {
userSliderStatus: boolean = false;
@ViewChild('profileBtn')
profileBtnElementRef!: ElementRef;
profileBtnElementRef!: ElementRef;
@ViewChild('profileDropdown')
profileDropdownElementRef!: ElementRef;
profileDropdownElementRef!: ElementRef;
@ViewChild('user')
userElementRef!: ElementRef;
userElementRef!: ElementRef;
loggedUser!: User | null;
@@ -46,25 +53,27 @@ export class HeaderComponent implements OnInit, OnDestroy {
private helpComponent!: ComponentRef<HelpComponent>;
constructor(private viewContainerRef: ViewContainerRef, private authService: AuthService) { }
constructor(
private viewContainerRef: ViewContainerRef,
private authService: AuthService,
) {}
ngOnInit(): void {
this.userSubscription = this.authService.authSubject.subscribe(
res => {
(res) => {
if (res && UserChecker.test(res)) {
this.loggedUser = <User>res;
} else {
this.loggedUser = null;
}
}
)
},
);
}
ngOnDestroy(): void {
this.userSubscription.unsubscribe();
}
public toogleProfileDropdown(): void {
this.profileDropdownState = !this.profileDropdownState;
}
@@ -121,22 +130,21 @@ export class HeaderComponent implements OnInit, OnDestroy {
}
private createLoginPopup(): void {
this.loginComponent = this.viewContainerRef.createComponent(LoginComponent);
this.loginComponent =
this.viewContainerRef.createComponent(LoginComponent);
this.loginComponent.instance.state = true;
this.loginComponent.instance.ignoreClickOutside = [
this.profileBtnElementRef,
this.profileDropdownElementRef,
this.userElementRef
].map(element => element.nativeElement);
this.userElementRef,
].map((element) => element.nativeElement);
this.loginComponent.instance.stateChange.subscribe(
state => {
if (!state) {
this.closeLoginPopup()
}
this.loginComponent.instance.stateChange.subscribe((state) => {
if (!state) {
this.closeLoginPopup();
}
);
});
this.navSliderStatus = false;
this.userSliderStatus = false;
@@ -144,22 +152,21 @@ export class HeaderComponent implements OnInit, OnDestroy {
}
private createSignupPopup() {
this.signupComponent = this.viewContainerRef.createComponent(SignupComponent);
this.signupComponent =
this.viewContainerRef.createComponent(SignupComponent);
this.signupComponent.instance.state = true;
this.signupComponent.instance.ignoreClickOutside = [
this.profileBtnElementRef,
this.profileDropdownElementRef,
this.userElementRef
].map(element => element.nativeElement);
this.userElementRef,
].map((element) => element.nativeElement);
this.signupComponent.instance.stateChange.subscribe(
state => {
if (!state) {
this.closeSignupPopup()
}
this.signupComponent.instance.stateChange.subscribe((state) => {
if (!state) {
this.closeSignupPopup();
}
);
});
this.navSliderStatus = false;
this.userSliderStatus = false;
@@ -167,17 +174,16 @@ export class HeaderComponent implements OnInit, OnDestroy {
}
private createMyProfilePopup() {
this.myProfileComponent = this.viewContainerRef.createComponent(MyProfileComponent);
this.myProfileComponent =
this.viewContainerRef.createComponent(MyProfileComponent);
this.myProfileComponent.instance.state = true;
this.myProfileComponent.instance.user = this.loggedUser;
this.myProfileComponent.instance.stateChange.subscribe(
state => {
if (!state) {
this.closeMyProfilePopup()
}
this.myProfileComponent.instance.stateChange.subscribe((state) => {
if (!state) {
this.closeMyProfilePopup();
}
);
});
this.navSliderStatus = false;
this.userSliderStatus = false;
@@ -185,16 +191,15 @@ export class HeaderComponent implements OnInit, OnDestroy {
}
private createHelpPopup() {
this.helpComponent = this.viewContainerRef.createComponent(HelpComponent);
this.helpComponent =
this.viewContainerRef.createComponent(HelpComponent);
this.helpComponent.instance.state = true;
this.helpComponent.instance.stateChange.subscribe(
state => {
if (!state) {
this.closeHelpPopup()
}
this.helpComponent.instance.stateChange.subscribe((state) => {
if (!state) {
this.closeHelpPopup();
}
);
});
this.navSliderStatus = false;
this.userSliderStatus = false;

View File

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

View File

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

View File

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

View File

@@ -1,17 +1,25 @@
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
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 { environment } from 'src/environments/environment';
import { HttpError } from '../model/httpError/httpError.model';
import { User } from '../model/user/user.model';
import * as http from "http";
import * as http from 'http';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class AuthService {
private userAuthenticated!: User;
authSubject = new Subject<User | HttpError | null>();
@@ -20,30 +28,35 @@ export class AuthService {
readonly BACKEND_OAUTH_PATH = environment.backendOAuthPath;
constructor(private http: HttpClient) { }
constructor(private http: HttpClient) {}
login(userAuthAtempt: User): void {
this.validateUser(this.loginUser(userAuthAtempt));
}
googleLogin() {
window.open(this.BACKEND_OAUTH_PATH + '/oauth2/authorization/google', '_self');
window.open(
this.BACKEND_OAUTH_PATH + '/oauth2/authorization/google',
'_self',
);
}
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 {
this.validateUser(this.fetchGoogleOAuthToken(p))
this.validateUser(this.fetchGoogleOAuthToken(p));
}
loginGithubUser(p: any): void {
this.validateUser(this.fetchGithubOAuthToken(p))
this.validateUser(this.fetchGithubOAuthToken(p));
}
signup(userAuthAtempt: User): void {
this.validateUser(this.createUser(userAuthAtempt));
}
refresh(): void {
@@ -56,7 +69,7 @@ export class AuthService {
logout() {
this.authSubject.next(null);
this.destroySessions().subscribe()
this.destroySessions().subscribe();
}
deleteAccount() {
@@ -66,7 +79,7 @@ export class AuthService {
addProfilePicture(file: File): void {
const fileType = file.type.split('/')[1];
this.getAddProfilePictureUrl(fileType).subscribe({
next: (url: string|null) => {
next: (url: string | null) => {
if (url != null) {
this.uploadProfilePicture(url, file).then(
(response: Observable<any>) => {
@@ -75,169 +88,160 @@ export class AuthService {
this.processProfilePicture().subscribe(
() => {
this.refresh();
}
},
);
}
},
});
}
},
);
}
}
})
},
});
}
private loginUser(userAuthAtempt: User): Observable<User|any> {
private loginUser(userAuthAtempt: User): Observable<User | any> {
let loginParams = new URLSearchParams();
loginParams.set("username", userAuthAtempt.username!);
loginParams.set("password", userAuthAtempt.password!);
loginParams.set('username', userAuthAtempt.username!);
loginParams.set('password', userAuthAtempt.password!);
let headers = new HttpHeaders({
'Content-Type': 'application/x-www-form-urlencoded'
'Content-Type': 'application/x-www-form-urlencoded',
});
return this.http.post<User>(
this.BACKEND_PATH + "/user/login",
loginParams,
{ headers: headers, withCredentials: true }
).pipe(
first()
)
return this.http
.post<User>(this.BACKEND_PATH + '/user/login', loginParams, {
headers: headers,
withCredentials: true,
})
.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(
{
fromObject: p
}
);
return this.http.get<User>(
this.BACKEND_OAUTH_PATH + '/login/oauth2/code/google',
{
return this.http
.get<User>(this.BACKEND_OAUTH_PATH + '/login/oauth2/code/google', {
withCredentials: true,
params: params
},
).pipe(
first()
);
params: params,
})
.pipe(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(
{
fromObject: p
}
);
return this.http.get<User>(
this.BACKEND_OAUTH_PATH + '/login/oauth2/code/github',
{
return this.http
.get<User>(this.BACKEND_OAUTH_PATH + '/login/oauth2/code/github', {
withCredentials: true,
params: params
},
).pipe(
first()
);
params: params,
})
.pipe(first());
}
private createUser(newUser: User) {
return this.http.post<User>(
this.BACKEND_PATH + "/user/signup",
newUser,
{ withCredentials: true }
).pipe(
first()
)
return this.http
.post<User>(this.BACKEND_PATH + '/user/signup', newUser, {
withCredentials: true,
})
.pipe(first());
}
private refreshAccessToken() {
return this.http.post<User>(
this.BACKEND_PATH + "/user/login/refresh",
this.BACKEND_PATH + '/user/login/refresh',
this.userAuthenticated.refreshToken,
{ withCredentials: true }
{ withCredentials: true },
);
}
private validateSession(): Observable<User> {
return this.http.get<User>(
this.BACKEND_PATH + '/session/validate',
{ 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);
}
return this.http.get<User>(this.BACKEND_PATH + '/session/validate', {
withCredentials: true,
});
}
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 destroySessions() {
return this.http.delete(this.BACKEND_PATH + '/session/destroy', {
withCredentials: true,
});
}
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);
let headers = new HttpHeaders({
'Content-Type': file.type
})
return this.http.put(
url,
fileData,
{
headers: headers,
}
);
'Content-Type': file.type,
});
return this.http.put(url, fileData, {
headers: headers,
});
}
private processProfilePicture() {
@@ -246,21 +250,22 @@ export class AuthService {
null,
{
headers: this.createAuthorizationHeader(),
withCredentials: true
}
)
withCredentials: true,
},
);
}
private createAuthorizationHeader(): HttpHeaders {
return new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + this.userAuthenticated.accessToken?.token
Authorization:
'Bearer ' + this.userAuthenticated.accessToken?.token,
});
}
private async readAsArrayBuffer(file: File): Promise<ArrayBuffer> {
const reader = new FileReader();
reader.readAsArrayBuffer(file)
reader.readAsArrayBuffer(file);
return new Promise<ArrayBuffer>((resolve, reject) => {
reader.onload = () => {
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;
font-family: 'Poppins', sans-serif;
font-family: "Poppins", sans-serif;
}
.popup-background {
@@ -51,7 +51,7 @@
background: #808080;
border-radius: 5px;
position: fixed;
content: '';
content: "";
width: 25px;
height: 5px;
}
@@ -73,11 +73,9 @@
height: 60px;
}
@media (min-width:767px) {
@media (min-width: 767px) {
.popup {
width: fit-content;
max-width: unset;
}
}

View File

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

View File

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

View File

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

View File

@@ -8,9 +8,8 @@ describe('SliderItemComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ SliderItemComponent ]
})
.compileComponents();
declarations: [SliderItemComponent],
}).compileComponents();
fixture = TestBed.createComponent(SliderItemComponent);
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';
@Component({
selector: 'app-slider-item',
templateUrl: './slider-item.component.html',
styleUrls: ['./slider-item.component.css'],
animations:[
trigger('animateSliderItem',[
state('hide', style({
'opacity': '0',
'transform': 'translateX(150px)'
}),
{
params: {
fadeInTime: 600,
fadeOutTime: 600
}
}),
state('show', style({
'opacity': '1',
'transform': 'translateX(0px)'
}),
{
params: {
fadeOutTime: 600,
fadeInTime: 600
animations: [
trigger('animateSliderItem', [
state(
'hide',
style({
opacity: '0',
transform: 'translateX(150px)',
}),
{
params: {
fadeInTime: 600,
fadeOutTime: 600,
},
},
}),
),
state(
'show',
style({
opacity: '1',
transform: 'translateX(0px)',
}),
{
params: {
fadeOutTime: 600,
fadeInTime: 600,
},
},
),
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 {
@Input()
public state:boolean = false;
constructor() { }
public state: boolean = false;
constructor() {}
get itemStatus(): string {
return this.state ? 'show' : 'hide';
}
}

View File

@@ -1,44 +1,42 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {NgcCookieConsentConfig, NgcCookieConsentModule} from "ngx-cookieconsent";
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
NgcCookieConsentConfig,
NgcCookieConsentModule,
} from 'ngx-cookieconsent';
const cookieConfig: NgcCookieConsentConfig = {
"cookie": {
"domain": "tinesoft.github.io"
cookie: {
domain: 'tinesoft.github.io',
},
"position": "bottom-left",
"theme": "classic",
"palette": {
"popup": {
"background": "#4e4e4e",
"text": "#ffffff",
"link": "#ffffff"
position: 'bottom-left',
theme: 'classic',
palette: {
popup: {
background: '#4e4e4e',
text: '#ffffff',
link: '#ffffff',
},
button: {
background: '#fa2f22',
text: '#ffffff',
border: 'transparent',
},
"button": {
"background": "#fa2f22",
"text": "#ffffff",
"border": "transparent"
}
},
"type": "opt-in",
"content": {
"message": "This website uses cookies to ensure you get the best experience on our website.",
"dismiss": "Got it!",
"deny": "Refuse cookies",
"link": "",
"href": "",
"policy": "Cookie Policy"
}
type: 'opt-in',
content: {
message:
'This website uses cookies to ensure you get the best experience on our website.',
dismiss: 'Got it!',
deny: 'Refuse cookies',
link: '',
href: '',
policy: 'Cookie Policy',
},
};
@NgModule({
declarations: [],
imports: [
CommonModule,
NgcCookieConsentModule.forRoot(cookieConfig)
]
imports: [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';
describe('CookieConsertService', () => {
let service: CookieConsertService;
let service: CookieConsertService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(CookieConsertService);
});
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(CookieConsertService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

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

View File

@@ -1,12 +1,28 @@
import { DOCUMENT } from '@angular/common';
import { AfterViewInit, Directive, ElementRef, EventEmitter, Inject, Input, OnDestroy, Output, ViewChild } from '@angular/core';
import { combineLatest, combineLatestWith, filter, fromEvent, merge, Subscription } from 'rxjs';
import {
AfterViewInit,
Directive,
ElementRef,
EventEmitter,
Inject,
Input,
OnDestroy,
Output,
ViewChild,
} from '@angular/core';
import {
combineLatest,
combineLatestWith,
filter,
fromEvent,
merge,
Subscription,
} from 'rxjs';
@Directive({
selector: '[appClickedOutside]'
selector: '[appClickedOutside]',
})
export class ClickedOutsideDirective implements AfterViewInit, OnDestroy {
@Input()
ignoreElementList!: HTMLDivElement[];
@@ -23,27 +39,27 @@ export class ClickedOutsideDirective implements AfterViewInit, OnDestroy {
constructor(
private element: ElementRef,
@Inject(DOCUMENT) private document: Document
) { }
@Inject(DOCUMENT) private document: Document,
) {}
ngAfterViewInit(): void {
const clickListener$ = fromEvent(this.document, 'click');
this.eventListener = clickListener$.pipe(
filter((click) => {
return (
(
this.isOutside(click.target as HTMLElement) ||
this.isInIncludedList(click.target as HTMLElement)
) &&
this.notInIgnoredList(click.target as HTMLElement)
);
})
). subscribe( () => {
!this.clickOutsideStopWatching && this.clickOutside.emit();
});
this.eventListener = clickListener$
.pipe(
filter((click) => {
return (
(this.isOutside(click.target as HTMLElement) ||
this.isInIncludedList(
click.target as HTMLElement,
)) &&
this.notInIgnoredList(click.target as HTMLElement)
);
}),
)
.subscribe(() => {
!this.clickOutsideStopWatching && this.clickOutside.emit();
});
}
ngOnDestroy(): void {
@@ -52,12 +68,14 @@ export class ClickedOutsideDirective implements AfterViewInit, OnDestroy {
private isOutside(elementToCheck: HTMLElement): boolean {
let status = true;
if (this.element.nativeElement === elementToCheck ||
this.element.nativeElement.contains(elementToCheck)) {
if (
this.element.nativeElement === elementToCheck ||
this.element.nativeElement.contains(elementToCheck)
) {
status = false;
}
console.log('isOutside', status)
console.log('isOutside', status);
return status;
}
@@ -68,26 +86,32 @@ export class ClickedOutsideDirective implements AfterViewInit, OnDestroy {
}
let validateIsIgnored = (ignoreElement: HTMLDivElement): boolean => {
return ignoreElement === elementToCheck
|| ignoreElement.contains(elementToCheck)
|| elementToCheck.contains(ignoreElement)
}
return (
ignoreElement === elementToCheck ||
ignoreElement.contains(elementToCheck) ||
elementToCheck.contains(ignoreElement)
);
};
return !this.ignoreElementList.some(validateIsIgnored)
return !this.ignoreElementList.some(validateIsIgnored);
}
private isInIncludedList(elementToCheck: HTMLElement): boolean {
if (!this.includeClickedOutside || this.includeClickedOutside.length === 0) {
if (
!this.includeClickedOutside ||
this.includeClickedOutside.length === 0
) {
return false;
}
let validateIsIncluded = (includedElement: HTMLDivElement): boolean => {
return includedElement === elementToCheck
|| includedElement.contains(elementToCheck)
|| elementToCheck.contains(includedElement)
}
return (
includedElement === elementToCheck ||
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`
*/
import * as t from "ts-interface-checker";
import * as t from 'ts-interface-checker';
// tslint:disable:object-literal-key-quotes
export const HttpError = t.iface([], {
"title": "string",
"status": "number",
"details": "string",
"developerMessage": "string",
"timestamp": "string",
title: 'string',
status: 'number',
details: 'string',
developerMessage: 'string',
timestamp: 'string',
});
const exportedTypeSuite: t.ITypeSuite = {

View File

@@ -4,4 +4,4 @@ export interface HttpError {
details: string;
developerMessage: string;
timestamp: string;
}
}

View File

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

View File

@@ -1,12 +1,12 @@
/**
* 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
export const Token = t.iface([], {
"token": "string",
"expirationDate": t.union("string", "number"),
token: 'string',
expirationDate: t.union('string', 'number'),
});
const exportedTypeSuite: t.ITypeSuite = {

View File

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

View File

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

View File

@@ -1,19 +1,19 @@
/**
* 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
export const User = t.iface([], {
"id": t.opt("number"),
"name": t.opt("string"),
"email": t.opt("string"),
"username": "string",
"password": t.opt("string"),
"profilePictureUrl": t.opt("string"),
"accessToken": t.opt("Token"),
"refreshToken": t.opt("Token"),
"roles": t.opt(t.array("string")),
id: t.opt('number'),
name: t.opt('string'),
email: t.opt('string'),
username: 'string',
password: t.opt('string'),
profilePictureUrl: t.opt('string'),
accessToken: t.opt('Token'),
refreshToken: t.opt('Token'),
roles: t.opt(t.array('string')),
});
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 {
id?: number,
name?: string,
email?: string,
username: string,
password?: string,
profilePictureUrl?: string,
accessToken?: Token,
refreshToken?: Token,
roles?: Array<string>,
id?: number;
name?: string;
email?: string;
username: string;
password?: string;
profilePictureUrl?: string;
accessToken?: Token;
refreshToken?: Token;
roles?: Array<string>;
validateAccessToken?: () => Token | undefined;
};
}

View File

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

View File

@@ -1,26 +1,27 @@
import { Injectable } from '@angular/core';
import {SwUpdate} from "@angular/service-worker";
import {interval} from "rxjs";
import { SwUpdate } from '@angular/service-worker';
import { interval } from 'rxjs';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class UpdateService {
constructor(private swUpdate: SwUpdate) {
if (swUpdate.isEnabled) {
interval(6 * 60 * 60).subscribe(() => swUpdate.checkForUpdate()
.then(() => console.log('checking for updates')));
interval(6 * 60 * 60).subscribe(() =>
swUpdate
.checkForUpdate()
.then(() => console.log('checking for updates')),
);
}
}
public checkForUpdates(): void {
this.swUpdate.available.subscribe(event => this.promptUser());
this.swUpdate.available.subscribe((event) => this.promptUser());
}
private promptUser(): void {
console.log('updating to new version');
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 { PopupComponent } from './components/popup/popup.component';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import {CookieConsentModule} from "./cookie-consent/cookie-consent.module";
import { CookieConsentModule } from './cookie-consent/cookie-consent.module';
@NgModule({
declarations: [
@@ -19,12 +19,8 @@ import {CookieConsentModule} from "./cookie-consent/cookie-consent.module";
HttpClientModule,
BrowserAnimationsModule,
FontAwesomeModule,
CookieConsentModule
CookieConsentModule,
],
exports: [
ClickedOutsideDirective,
SliderItemComponent,
PopupComponent
]
exports: [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) {
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) {
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) {
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);
if (!passwordValid) {
return { invalidPassword: true };

View File

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

View File

@@ -1,5 +1,5 @@
export const environment = {
production: true,
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 = {
production: false,
backendPath: 'http://localhost:8070',
backendOAuthPath: 'http://localhost:8070'
backendOAuthPath: 'http://localhost:8070',
};
/*

View File

@@ -1,71 +1,115 @@
@font-face {
font-family: 'Montserrat';
font-style: italic;
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');
font-family: "Montserrat";
font-style: italic;
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");
}
@font-face {
font-family: 'Montserrat';
font-style: italic;
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');
font-family: "Montserrat";
font-style: italic;
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");
}
@font-face {
font-family: 'Montserrat';
font-style: italic;
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');
font-family: "Montserrat";
font-style: italic;
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");
}
@font-face {
font-family: 'Montserrat';
font-style: italic;
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');
font-family: "Montserrat";
font-style: italic;
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");
}
@font-face {
font-family: 'Montserrat';
font-style: italic;
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');
font-family: "Montserrat";
font-style: italic;
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");
}
@font-face {
font-family: 'Montserrat';
font-style: normal;
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');
font-family: "Montserrat";
font-style: normal;
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");
}
@font-face {
font-family: 'Montserrat';
font-style: normal;
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');
font-family: "Montserrat";
font-style: normal;
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");
}
@font-face {
font-family: 'Montserrat';
font-style: normal;
font-weight: 400;
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');
font-family: "Montserrat";
font-style: normal;
font-weight: 400;
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");
}
@font-face {
font-family: 'Montserrat';
font-style: normal;
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');
font-family: "Montserrat";
font-style: normal;
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");
}
@font-face {
font-family: 'Montserrat';
font-style: normal;
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');
font-family: "Montserrat";
font-style: normal;
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");
}

View File

@@ -1,31 +1,39 @@
<!DOCTYPE html>
<!doctype html>
<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 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">
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<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" />
<link
rel="preconnect"
href="https://hideyoshi-portfolio-dev.s3.amazonaws.com"
/>
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<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">
<!-- Loading environment variables -->
<script src="./assets/env.js"></script>
</head>
<link rel="manifest" href="manifest.webmanifest">
<link rel="preconnect" href="https://hideyoshi-portfolio-dev.s3.amazonaws.com">
<!-- Loading environment variables -->
<script src="./assets/env.js"></script>
</head>
<body>
<app-root></app-root>
<noscript>Please enable JavaScript to continue using this application.</noscript>
</body>
<body>
<app-root></app-root>
<noscript
>Please enable JavaScript to continue using this
application.</noscript
>
</body>
</html>

View File

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

View File

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

View File

@@ -45,8 +45,7 @@
/***************************************************************************************************
* 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

View File

@@ -1,11 +1,16 @@
/* 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;
margin: 0 auto;
}
h1, h2, h3, h4, h5, h6 {
font-family: 'Montserrat', sans-serif;
h1,
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 {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
platformBrowserDynamicTesting,
} from '@angular/platform-browser-dynamic/testing';
// First, initialize the Angular testing environment.

View File

@@ -1,15 +1,10 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": ["node"]
},
"files": [
"src/main.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.d.ts"
]
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": ["node"]
},
"files": ["src/main.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. */
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "ES2022",
"module": "es2020",
"lib": [
"es2020",
"dom"
],
"useDefineForClassFields": false
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "ES2022",
"module": "es2020",
"lib": ["es2020", "dom"],
"useDefineForClassFields": false
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}

View File

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