FrontEnd Angular - v0.0.1-alpha

This commit is contained in:
2022-09-04 04:22:18 -03:00
parent 9640501195
commit 4a0ff02e2a
116 changed files with 27771 additions and 19001 deletions

View File

@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { AuthService } from './auth.service';
describe('AuthService', () => {
let service: AuthService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(AuthService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@@ -0,0 +1,118 @@
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { first, firstValueFrom, Observable, Subject, take } from 'rxjs';
import { environment } from 'src/environments/environment';
import { HttpError } from '../model/httpError/httpError.model';
import { Token } from '../model/token/token.model';
import { User } from '../model/user/user.model';
@Injectable({
providedIn: 'root'
})
export class AuthService {
private userAuthenticated!: User;
authSubject = new Subject<User|HttpError|null>();
readonly BACKEND_PATH = environment.backendPath;
constructor(private http: HttpClient) { }
login(userAuthAtempt: User): void {
this.validateUser(this.loginUser(userAuthAtempt));
}
signup(userAuthAtempt: User): void {
this.validateUser(this.createUser(userAuthAtempt));
}
autoLogin(): void {
this.validateUser(this.validateSession());
}
logout() {
this.authSubject.next(null);
this.destroySessions().subscribe()
}
async getUserAccessToken(): Promise<Token | undefined> {
if (this.userAuthenticated) {
if ((!this.userAuthenticated.accessToken && this.refreshAccessToken ) ||
(this.userAuthenticated.accessToken && this.userAuthenticated.accessToken.expirationDate < Date.now())) {
this.userAuthenticated = <User>(await this.refreshAccessToken());
}
return this.userAuthenticated.accessToken;
} else return
}
private loginUser(userAuthAtempt: User): Observable<User|any> {
let loginParams = new URLSearchParams();
loginParams.set("username", userAuthAtempt.username!);
loginParams.set("password", userAuthAtempt.password!);
let headers = new HttpHeaders({
'Content-Type': 'application/x-www-form-urlencoded'
});
return this.http.post<User>(
this.BACKEND_PATH + "/user/login",
loginParams,
{ headers: headers, withCredentials: true }
).pipe(
first()
)
}
private createUser(newUser: User) {
return this.http.post<User>(
this.BACKEND_PATH + "/user/signup",
newUser,
{ withCredentials: true }
).pipe(
first()
)
}
private validateUser(userAuthAtempt: Observable<User>) {
userAuthAtempt.subscribe({
next: userAuthentication => {
this.userAuthenticated = <User>userAuthentication;
this.authSubject.next(this.userAuthenticated);
},
error: err => {
this.authSubject.next(<HttpError>err.error);
}
});
}
private refreshAccessToken() {
return firstValueFrom(this.http.post(
this.BACKEND_PATH + "/user/login/refresh",
this.userAuthenticated.refreshToken,
{ withCredentials: true }
));
}
private validateSession(): Observable<User> {
return this.http.get<User>(
this.BACKEND_PATH + '/session/validate',
{ withCredentials: true }
).pipe(
first()
);
}
private destroySessions() {
return this.http.post(
this.BACKEND_PATH + '/session/destroy',
{},
{ withCredentials: true }
).pipe(
take(1)
);
}
}

View File

@@ -0,0 +1,83 @@
@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;
}
.popup-background {
background-color: rgba(0, 0, 0, 0.5);
justify-content: center;
align-content: center;
align-items: center;
overflow-y: hidden;
position: fixed;
display: flex;
height: 100vh;
width: 100vw;
left: 0;
top: 0;
}
.popup {
box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;
background-color: #ffffff;
border: 1px solid #dddd;
border-radius: 18px;
height: fit-content;
position: relative;
max-width: 400px;
overflow: auto;
width: 90%;
}
.popup-header {
justify-content: right;
align-items: center;
display: flex;
height: 60px;
width: 100%;
}
.popup-close-btn {
margin: 5px 20px;
cursor: pointer;
height: 20px;
width: 20px;
}
.popup-close-btn::before,
.popup-close-btn::after {
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.271);
background: #808080;
border-radius: 5px;
position: fixed;
content: '';
width: 25px;
height: 5px;
}
.popup-close-btn::before {
transform: translateY(150%) translateX(-10%) rotate(45deg);
}
.popup-close-btn::after {
transform: translateY(150%) translateX(-10%) rotate(-45deg);
}
.popup-body {
height: calc(100% - 120px);
padding: 0px 60px;
width: 100%;
margin: 0;
}
.popup-footer {
height: 60px;
}
@media (min-width:767px) {
.popup {
width: fit-content;
max-width: unset;
}
}

View File

@@ -0,0 +1,17 @@
<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>
<div class="popup-body">
<ng-content></ng-content>
</div>
<div class="popup-footer"></div>
</div>
</div>

View File

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

View File

@@ -0,0 +1,69 @@
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',
templateUrl: './popup.component.html',
styleUrls: ['./popup.component.css'],
animations: [
trigger('popupState', [
state('hide', style({
'opacity': '0'
})),
state('show', style({
'opacity': '1'
})),
transition(
'* => show',
group([
query(
"@*",
animateChild(),
{ optional: true }
),
animate('125ms ease-in')
])
),
transition(
'show => hide',
group([
query(
"@*",
animateChild(),
{ optional: true }
),
animate('250ms ease-out')
])
)
])
]
})
export class PopupComponent {
@Input()
state: boolean = false;
@Input()
ignoreClickOutside!: HTMLDivElement[];
@Output()
stateChange = new EventEmitter<boolean>(false);
constructor() { }
get popupState(): string {
return this.state ? 'show' : 'hide';
}
animationStop() {
if (!this.state) {
this.closePopup()
this.stateChange.emit(false);
}
}
closePopup(): void {
this.state = false;
}
}

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SliderItemComponent } from './slider-item.component';
describe('SliderItemComponent', () => {
let component: SliderItemComponent;
let fixture: ComponentFixture<SliderItemComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ SliderItemComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(SliderItemComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,46 @@
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
},
}),
transition('hide => show', animate(`{{ fadeInTime }}s ease-in`)),
transition('show => hide', animate(`{{ fadeOutTime }}s ease-out`))
])
]
})
export class SliderItemComponent {
@Input()
public state:boolean = false;
constructor() { }
get itemStatus(): string {
return this.state ? 'show' : 'hide';
}
}

View File

@@ -0,0 +1,8 @@
import { ClickedOutsideDirective } from './clicked-outside.directive';
describe('ClickedOutsideDirective', () => {
it('should create an instance', () => {
const directive = new ClickedOutsideDirective();
expect(directive).toBeTruthy();
});
});

View File

@@ -0,0 +1,74 @@
import { DOCUMENT } from '@angular/common';
import { AfterViewInit, Directive, ElementRef, EventEmitter, Inject, Input, OnDestroy, Output, ViewChild } from '@angular/core';
import { filter, fromEvent, Subscription } from 'rxjs';
@Directive({
selector: '[appClickedOutside]'
})
export class ClickedOutsideDirective implements AfterViewInit, OnDestroy {
@Input()
ignoreElementList!: HTMLDivElement[];
@Input()
includeClickedOutside!: HTMLDivElement[];
@Input()
clickOutsideStopWatching: boolean = false;
@Output()
clickOutside: EventEmitter<void> = new EventEmitter();
clickListener!: Subscription;
constructor(
private element: ElementRef,
@Inject(DOCUMENT) private document: Document
) { }
ngAfterViewInit(): void {
this.clickListener = fromEvent(this.document, 'click')
.pipe(
filter((event) => {
return !this.isInside(event.target as HTMLElement) || this.includedList(event.target as HTMLElement);
})
). subscribe( () => {
!this.clickOutsideStopWatching && this.clickOutside.emit();
});
}
ngOnDestroy(): void {
this.clickListener?.unsubscribe();
}
private isInside(elementToCheck: HTMLElement): boolean {
return (
elementToCheck === this.element.nativeElement
|| this.element.nativeElement.contains(elementToCheck)
|| (this.ignoreElementList && this.checkIgnoredList(elementToCheck))
);
}
private checkIgnoredList(elementToCheck: HTMLElement): boolean {
return this.ignoreElementList.some(
(ignoreElement) => {
return ignoreElement === elementToCheck ||
ignoreElement.contains(elementToCheck)
}
)
}
private includedList(elementToCheck: HTMLElement): boolean {
return this.includeClickedOutside && this.includeClickedOutside.some(
(includedElement) => {
return includedElement === elementToCheck ||
includedElement.contains(elementToCheck)
}
)
}
}

View File

@@ -0,0 +1,18 @@
/**
* This module was automatically generated by `ts-interface-builder`
*/
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",
});
const HttpErrorTI: t.ITypeSuite = {
HttpError,
};
export default HttpErrorTI;

View File

@@ -0,0 +1,7 @@
export interface HttpError {
title: string;
status: number;
details: string;
developerMessage: string;
timestamp: string;
}

View File

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

View File

@@ -0,0 +1,15 @@
/**
* This module was automatically generated by `ts-interface-builder`
*/
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"),
});
const exportedTypeSuite: t.ITypeSuite = {
Token,
};
export default exportedTypeSuite;

View File

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

View File

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

View File

@@ -0,0 +1,23 @@
/**
* This module was automatically generated by `ts-interface-builder`
*/
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"),
"accessToken": t.opt("Token"),
"refreshToken": t.opt("Token"),
"authorities": t.opt(t.array(t.iface([], {
"authority": "string",
}))),
});
const UserTI: t.ITypeSuite = {
User,
};
export default UserTI;

View File

@@ -0,0 +1,13 @@
import { Token } from "../token/token.model";
export interface User {
id?: number,
fullname?: string,
email?: string,
username: string,
password?: string,
accessToken?: Token,
refreshToken?: Token,
authorities?: Array<{authority: string}>,
validateAccessToken?: () => Token | undefined;
};

View File

@@ -0,0 +1,28 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ClickedOutsideDirective } from './directive/clicked-outside/clicked-outside.directive';
import { SliderItemComponent } from './components/slider-item/slider-item.component';
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';
@NgModule({
declarations: [
ClickedOutsideDirective,
SliderItemComponent,
PopupComponent
],
imports: [
CommonModule,
HttpClientModule,
BrowserAnimationsModule,
FontAwesomeModule,
],
exports: [
ClickedOutsideDirective,
SliderItemComponent,
PopupComponent
]
})
export class SharedModule { }