This commit is contained in:
Jan K9f 2025-08-20 08:29:13 +02:00
commit 03ddce0290
Signed by: jank
GPG key ID: 22BEAC760B3333D6
25 changed files with 240 additions and 165 deletions

View file

@ -16,9 +16,7 @@
"outputPath": "dist/auth", "outputPath": "dist/auth",
"index": "src/index.html", "index": "src/index.html",
"browser": "src/main.ts", "browser": "src/main.ts",
"polyfills": [ "polyfills": ["zone.js"],
"zone.js"
],
"tsConfig": "tsconfig.app.json", "tsConfig": "tsconfig.app.json",
"assets": [ "assets": [
{ {
@ -74,10 +72,7 @@
"test": { "test": {
"builder": "@angular/build:karma", "builder": "@angular/build:karma",
"options": { "options": {
"polyfills": [ "polyfills": ["zone.js", "zone.js/testing"],
"zone.js",
"zone.js/testing"
],
"tsConfig": "tsconfig.spec.json", "tsConfig": "tsconfig.spec.json",
"assets": [ "assets": [
{ {

View file

@ -6,5 +6,4 @@ import { RouterOutlet } from '@angular/router';
imports: [RouterOutlet], imports: [RouterOutlet],
templateUrl: './app.component.html', templateUrl: './app.component.html',
}) })
export class AppComponent { export class AppComponent {}
}

View file

@ -1,14 +1,14 @@
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import {provideRouter, withDebugTracing} from '@angular/router'; import { provideRouter, withDebugTracing } from '@angular/router';
import { routes } from './app.routes'; import { routes } from './app.routes';
import {provideHttpClient, withInterceptors} from "@angular/common/http"; import { provideHttpClient, withInterceptors } from '@angular/common/http';
import {httpInterceptor} from "./service/http.interceptor"; import { httpInterceptor } from './service/http.interceptor';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [ providers: [
provideZoneChangeDetection({ eventCoalescing: true }), provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes, withDebugTracing()), provideRouter(routes, withDebugTracing()),
provideHttpClient(withInterceptors([httpInterceptor])), provideHttpClient(withInterceptors([httpInterceptor])),
] ],
}; };

View file

@ -1,5 +1,5 @@
import { Routes } from '@angular/router'; import { Routes } from '@angular/router';
import {authGuard} from "./service/auth.guard"; import { authGuard } from './service/auth.guard';
export const routes: Routes = [ export const routes: Routes = [
{ {
@ -7,7 +7,8 @@ export const routes: Routes = [
children: [ children: [
{ {
path: 'register', path: 'register',
loadComponent: () => import('./component/auth/register/register.component'), loadComponent: () =>
import('./component/auth/register/register.component'),
}, },
{ {
path: 'login', path: 'login',
@ -21,7 +22,8 @@ export const routes: Routes = [
children: [ children: [
{ {
path: 'dashboard', path: 'dashboard',
loadComponent: () => import('./component/dashboard/dashboard.component'), loadComponent: () =>
import('./component/dashboard/dashboard.component'),
}, },
{ {
path: 'users', path: 'users',
@ -29,5 +31,5 @@ export const routes: Routes = [
}, },
], ],
canActivate: [authGuard], canActivate: [authGuard],
} },
]; ];

View file

@ -8,13 +8,25 @@
<form [formGroup]="form"> <form [formGroup]="form">
<div class="mb-3"> <div class="mb-3">
<label for="username" class="form-label">Username</label> <label for="username" class="form-label">Username</label>
<input type="text" id="username" class="form-control" formControlName="username"> <input
type="text"
id="username"
class="form-control"
formControlName="username"
/>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="password" class="form-label">Password</label> <label for="password" class="form-label">Password</label>
<input type="password" id="password" class="form-control" formControlName="password"> <input
type="password"
id="password"
class="form-control"
formControlName="password"
/>
</div> </div>
<button type="submit" class="btn btn-primary" (click)="login()">Login</button> <button type="submit" class="btn btn-primary" (click)="login()">
Login
</button>
</form> </form>
</div> </div>
</div> </div>

View file

@ -1,14 +1,17 @@
import {Component, inject} from '@angular/core'; import { Component, inject } from '@angular/core';
import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from "@angular/forms"; import {
import AuthService from "../../../service/auth.service"; FormBuilder,
import {AuthResponse, Token} from "../../../models"; FormGroup,
import {Router} from "@angular/router"; ReactiveFormsModule,
Validators,
} from '@angular/forms';
import AuthService from '../../../service/auth.service';
import { AuthResponse, Token } from '../../../models';
import { Router } from '@angular/router';
@Component({ @Component({
selector: 'app-login', selector: 'app-login',
imports: [ imports: [ReactiveFormsModule],
ReactiveFormsModule
],
templateUrl: './login.component.html', templateUrl: './login.component.html',
}) })
export default class LoginComponent { export default class LoginComponent {
@ -23,7 +26,7 @@ export default class LoginComponent {
login() { login() {
if (this.form.invalid) { if (this.form.invalid) {
console.log(this.form.errors) console.log(this.form.errors);
return; return;
} }
@ -31,7 +34,7 @@ export default class LoginComponent {
localStorage.setItem('access_token', r.accessToken); localStorage.setItem('access_token', r.accessToken);
localStorage.setItem('refresh_token', r.refreshToken); localStorage.setItem('refresh_token', r.refreshToken);
this.router.navigate(['dashboard']) this.router.navigate(['dashboard']);
}); });
} }
} }

View file

@ -9,11 +9,21 @@
<div class="row"> <div class="row">
<div class="col-md-6 mb-3"> <div class="col-md-6 mb-3">
<label for="firstname" class="form-label">First Name</label> <label for="firstname" class="form-label">First Name</label>
<input id="firstname" class="form-control" formControlName="firstname" type="text"> <input
id="firstname"
class="form-control"
formControlName="firstname"
type="text"
/>
</div> </div>
<div class="col-md-6 mb-3"> <div class="col-md-6 mb-3">
<label for="lastname" class="form-label">Last Name</label> <label for="lastname" class="form-label">Last Name</label>
<input id="lastname" class="form-control" formControlName="lastname" type="text"> <input
id="lastname"
class="form-control"
formControlName="lastname"
type="text"
/>
</div> </div>
</div> </div>
@ -22,25 +32,50 @@
<div class="row"> <div class="row">
<div class="col-md-8 mb-3"> <div class="col-md-8 mb-3">
<label for="street" class="form-label">Street</label> <label for="street" class="form-label">Street</label>
<input id="street" class="form-control" formControlName="street" type="text"> <input
id="street"
class="form-control"
formControlName="street"
type="text"
/>
</div> </div>
<div class="col-md-4 mb-3"> <div class="col-md-4 mb-3">
<label for="nr" class="form-label">Number</label> <label for="nr" class="form-label">Number</label>
<input id="nr" class="form-control" formControlName="nr" type="text"> <input
id="nr"
class="form-control"
formControlName="nr"
type="text"
/>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-md-6 mb-3"> <div class="col-md-6 mb-3">
<label for="city" class="form-label">City</label> <label for="city" class="form-label">City</label>
<input id="city" class="form-control" formControlName="city" type="text"> <input
id="city"
class="form-control"
formControlName="city"
type="text"
/>
</div> </div>
<div class="col-md-3 mb-3"> <div class="col-md-3 mb-3">
<label for="state" class="form-label">State</label> <label for="state" class="form-label">State</label>
<input id="state" class="form-control" formControlName="state" type="text"> <input
id="state"
class="form-control"
formControlName="state"
type="text"
/>
</div> </div>
<div class="col-md-3 mb-3"> <div class="col-md-3 mb-3">
<label for="zip" class="form-label">ZIP Code</label> <label for="zip" class="form-label">ZIP Code</label>
<input id="zip" class="form-control" formControlName="zip" type="text"> <input
id="zip"
class="form-control"
formControlName="zip"
type="text"
/>
</div> </div>
</div> </div>
</div> </div>
@ -50,11 +85,23 @@
<div class="row"> <div class="row">
<div class="col-md-6 mb-3"> <div class="col-md-6 mb-3">
<label for="email" class="form-label">Email</label> <label for="email" class="form-label">Email</label>
<input id="email" class="form-control" formControlName="email" type="email"> <input
id="email"
class="form-control"
formControlName="email"
type="email"
/>
</div> </div>
<div class="col-md-6 mb-3"> <div class="col-md-6 mb-3">
<label for="confirmEmail" class="form-label">Confirm Email</label> <label for="confirmEmail" class="form-label"
<input id="confirmEmail" class="form-control" formControlName="confirmEmail" type="email"> >Confirm Email</label
>
<input
id="confirmEmail"
class="form-control"
formControlName="confirmEmail"
type="email"
/>
</div> </div>
</div> </div>
</div> </div>
@ -64,16 +111,30 @@
<div class="row"> <div class="row">
<div class="col-md-6 mb-3"> <div class="col-md-6 mb-3">
<label for="password" class="form-label">Password</label> <label for="password" class="form-label">Password</label>
<input id="password" class="form-control" formControlName="password" type="password"> <input
id="password"
class="form-control"
formControlName="password"
type="password"
/>
</div> </div>
<div class="col-md-6 mb-3"> <div class="col-md-6 mb-3">
<label for="confirmPassword" class="form-label">Confirm Password</label> <label for="confirmPassword" class="form-label"
<input id="confirmPassword" class="form-control" formControlName="confirmPassword" type="password"> >Confirm Password</label
>
<input
id="confirmPassword"
class="form-control"
formControlName="confirmPassword"
type="password"
/>
</div> </div>
</div> </div>
</div> </div>
<button type="submit" class="btn btn-primary mt-3" (click)="submit()">Submit</button> <button type="submit" class="btn btn-primary mt-3" (click)="submit()">
Submit
</button>
</form> </form>
</div> </div>
</div> </div>

View file

@ -1,13 +1,16 @@
import {Component, inject} from '@angular/core'; import { Component, inject } from '@angular/core';
import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from "@angular/forms"; import {
import AuthService from "../../../service/auth.service"; FormBuilder,
import CustomValidators from "../../../service/custom.validators"; FormGroup,
ReactiveFormsModule,
Validators,
} from '@angular/forms';
import AuthService from '../../../service/auth.service';
import CustomValidators from '../../../service/custom.validators';
@Component({ @Component({
selector: 'app-register', selector: 'app-register',
imports: [ imports: [ReactiveFormsModule],
ReactiveFormsModule
],
templateUrl: './register.component.html', templateUrl: './register.component.html',
}) })
export default class RegisterComponent { export default class RegisterComponent {

View file

@ -1,7 +1,9 @@
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4"> <main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"> <div
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"
>
<h1 class="h2">Dashboard</h1> <h1 class="h2">Dashboard</h1>
</div> </div>

View file

@ -1,14 +1,10 @@
import {Component} from '@angular/core'; import { Component } from '@angular/core';
import "../side/side.component"; import '../side/side.component';
import {RouterOutlet} from "@angular/router"; import { RouterOutlet } from '@angular/router';
@Component({ @Component({
selector: 'app-dashboard', selector: 'app-dashboard',
imports: [ imports: [RouterOutlet],
RouterOutlet
],
templateUrl: './dashboard.component.html', templateUrl: './dashboard.component.html',
}) })
export default class DashboardComponent { export default class DashboardComponent {}
}

View file

@ -1,7 +1,15 @@
<nav class="navbar navbar-expand-lg navbar-dark bg-dark"> <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand" href="#">Auth App</a> <a class="navbar-brand" href="#">Auth App</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> <button
class="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarNav"
aria-controls="navbarNav"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarNav"> <div class="collapse navbar-collapse" id="navbarNav">

View file

@ -1,14 +1,10 @@
import {Component} from '@angular/core'; import { Component } from '@angular/core';
import {RouterLink} from "@angular/router"; import { RouterLink } from '@angular/router';
@Component({ @Component({
selector: 'app-header', selector: 'app-header',
imports: [ imports: [RouterLink],
RouterLink
],
templateUrl: './header.component.html', templateUrl: './header.component.html',
styleUrl: './header.component.css' styleUrl: './header.component.css',
}) })
export default class HeaderComponent { export default class HeaderComponent {}
}

View file

@ -1,17 +1,11 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import HeaderComponent from "../header/header.component"; import HeaderComponent from '../header/header.component';
import SideComponent from "../side/side.component"; import SideComponent from '../side/side.component';
import {RouterOutlet} from "@angular/router"; import { RouterOutlet } from '@angular/router';
@Component({ @Component({
selector: 'app-random-ahh', selector: 'app-random-ahh',
imports: [ imports: [HeaderComponent, SideComponent, RouterOutlet],
HeaderComponent,
SideComponent,
RouterOutlet
],
templateUrl: './random-ahh.component.html', templateUrl: './random-ahh.component.html',
}) })
export default class RandomAhhComponent { export default class RandomAhhComponent {}
}

View file

@ -1,4 +1,7 @@
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse"> <nav
id="sidebarMenu"
class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse"
>
<div class="position-sticky pt-3"> <div class="position-sticky pt-3">
<ul class="nav flex-column"> <ul class="nav flex-column">
<li class="nav-item"> <li class="nav-item">

View file

@ -4,8 +4,6 @@ import { Component } from '@angular/core';
selector: 'app-side', selector: 'app-side',
imports: [], imports: [],
templateUrl: './side.component.html', templateUrl: './side.component.html',
styleUrl: './side.component.css' styleUrl: './side.component.css',
}) })
export default class SideComponent { export default class SideComponent {}
}

View file

@ -5,6 +5,4 @@ import { Component } from '@angular/core';
imports: [], imports: [],
templateUrl: './users.component.html', templateUrl: './users.component.html',
}) })
export default class UsersComponent { export default class UsersComponent {}
}

View file

@ -1,11 +1,17 @@
import {CanActivateFn} from "@angular/router"; import { CanActivateFn } from '@angular/router';
import {inject} from "@angular/core"; import { inject } from '@angular/core';
import {HttpClient, HttpErrorResponse, HttpResponse} from "@angular/common/http"; import {
import {catchError, map, of} from "rxjs"; HttpClient,
import {User} from "../models"; HttpErrorResponse,
HttpResponse,
} from '@angular/common/http';
import { catchError, map, of } from 'rxjs';
import { User } from '../models';
export const authGuard: CanActivateFn = () => { export const authGuard: CanActivateFn = () => {
return inject(HttpClient).get<User>('https://dummyjson.com/auth/me', { observe: 'response' }).pipe( return inject(HttpClient)
.get<User>('https://dummyjson.com/auth/me', { observe: 'response' })
.pipe(
map((res: HttpResponse<User>) => { map((res: HttpResponse<User>) => {
console.log(res); console.log(res);
return res.status === 200; return res.status === 200;
@ -13,6 +19,6 @@ export const authGuard: CanActivateFn = () => {
catchError((err: HttpErrorResponse) => { catchError((err: HttpErrorResponse) => {
console.log(err); console.log(err);
return of(false); return of(false);
}) }),
); );
}; };

View file

@ -1,7 +1,6 @@
import {inject, Injectable} from "@angular/core"; import { inject, Injectable } from '@angular/core';
import {HttpClient} from "@angular/common/http"; import { HttpClient } from '@angular/common/http';
import {AuthResponse} from "../models"; import { AuthResponse } from '../models';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@ -9,13 +8,14 @@ import {AuthResponse} from "../models";
export default class AuthService { export default class AuthService {
http: HttpClient = inject(HttpClient); http: HttpClient = inject(HttpClient);
register(data: { email: string, password: string }): void { register(data: { email: string; password: string }): void {
console.log(data); console.log(data);
} }
login(loginData: { username: string, password: string }) { login(loginData: { username: string; password: string }) {
return this.http.post<AuthResponse>('https://dummyjson.com/auth/login', loginData) return this.http.post<AuthResponse>(
'https://dummyjson.com/auth/login',
loginData,
);
} }
} }

View file

@ -1,31 +1,32 @@
import {AbstractControl, ValidatorFn} from "@angular/forms"; import { AbstractControl, ValidatorFn } from '@angular/forms';
export default class CustomValidators { export default class CustomValidators {
public static email(): ValidatorFn { public static email(): ValidatorFn {
return (emailGroup: AbstractControl) => { return (emailGroup: AbstractControl) => {
if (emailGroup.get('email')?.value !== emailGroup.get('confirmEmail')) { if (emailGroup.get('email')?.value !== emailGroup.get('confirmEmail')) {
return {emailMismatch: true} return { emailMismatch: true };
} }
return {}; return {};
} };
} }
public static password(): ValidatorFn { public static password(): ValidatorFn {
return (passwordGroup: AbstractControl) => { return (passwordGroup: AbstractControl) => {
if (passwordGroup.get('password')?.value !== passwordGroup.get('confirmPassword')) { if (
return {emailMismatch: true} passwordGroup.get('password')?.value !==
passwordGroup.get('confirmPassword')
) {
return { emailMismatch: true };
} }
return {}; return {};
} };
} }
static address(): ValidatorFn { static address(): ValidatorFn {
return (addressGroup: AbstractControl) => { return (addressGroup: AbstractControl) => {
return {}; return {};
} };
} }
} }

View file

@ -1,9 +1,18 @@
import {HttpHandlerFn, HttpInterceptorFn, HttpRequest} from "@angular/common/http"; import {
HttpHandlerFn,
HttpInterceptorFn,
HttpRequest,
} from '@angular/common/http';
export const httpInterceptor: HttpInterceptorFn = (req: HttpRequest<unknown>, next: HttpHandlerFn) => { export const httpInterceptor: HttpInterceptorFn = (
return next(req.clone({ req: HttpRequest<unknown>,
next: HttpHandlerFn,
) => {
return next(
req.clone({
setHeaders: { setHeaders: {
Authorization: "Bearer " + localStorage.getItem('access_token') Authorization: 'Bearer ' + localStorage.getItem('access_token'),
} },
})); }),
} );
};

View file

@ -1,13 +1,13 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8" />
<title>Auth</title> <title>Auth</title>
<base href="/"> <base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico"> <link rel="icon" type="image/x-icon" href="favicon.ico" />
</head> </head>
<body> <body>
<app-root></app-root> <app-root></app-root>
</body> </body>
</html> </html>

View file

@ -2,5 +2,6 @@ import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config'; import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component'; import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, appConfig) bootstrapApplication(AppComponent, appConfig).catch((err) =>
.catch((err) => console.error(err)); console.error(err),
);

View file

@ -6,10 +6,6 @@
"outDir": "./out-tsc/app", "outDir": "./out-tsc/app",
"types": [] "types": []
}, },
"files": [ "files": ["src/main.ts"],
"src/main.ts" "include": ["src/**/*.d.ts"]
],
"include": [
"src/**/*.d.ts"
]
} }

View file

@ -18,10 +18,7 @@
"importHelpers": true, "importHelpers": true,
"target": "ES2022", "target": "ES2022",
"module": "ES2022", "module": "ES2022",
"lib": [ "lib": ["ES2022", "dom"]
"ES2022",
"dom"
]
}, },
"angularCompilerOptions": { "angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false, "enableI18nLegacyMessageIdFormat": false,

View file

@ -4,12 +4,7 @@
"extends": "./tsconfig.json", "extends": "./tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "./out-tsc/spec", "outDir": "./out-tsc/spec",
"types": [ "types": ["jasmine"]
"jasmine"
]
}, },
"include": [ "include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
} }