Merge pull request 'feature/init-tailwind-daisyui' (#13) from feature/init-tailwind-daisyui into main
Reviewed-on: #13
This commit is contained in:
commit
b7f73033f3
|
@ -29,6 +29,7 @@
|
||||||
"@angular/platform-browser-dynamic": "^17.3.0",
|
"@angular/platform-browser-dynamic": "^17.3.0",
|
||||||
"@angular/router": "^17.3.0",
|
"@angular/router": "^17.3.0",
|
||||||
"@types/dompurify": "^3.0.5",
|
"@types/dompurify": "^3.0.5",
|
||||||
|
"chroma-js": "^2.4.2",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"dompurify": "^3.1.3",
|
"dompurify": "^3.1.3",
|
||||||
"primeicons": "^7.0.0",
|
"primeicons": "^7.0.0",
|
||||||
|
@ -46,11 +47,14 @@
|
||||||
"@angular-eslint/template-parser": "17.2.1",
|
"@angular-eslint/template-parser": "17.2.1",
|
||||||
"@angular/cli": "^17.3.0",
|
"@angular/cli": "^17.3.0",
|
||||||
"@angular/compiler-cli": "^17.3.0",
|
"@angular/compiler-cli": "^17.3.0",
|
||||||
|
"@types/chroma-js": "^2.4.4",
|
||||||
"@types/crypto-js": "^4.2.2",
|
"@types/crypto-js": "^4.2.2",
|
||||||
"@types/jest": "^29.5.12",
|
"@types/jest": "^29.5.12",
|
||||||
"@typescript-eslint/eslint-plugin": "6.19.0",
|
"@typescript-eslint/eslint-plugin": "6.19.0",
|
||||||
"@typescript-eslint/parser": "6.19.0",
|
"@typescript-eslint/parser": "6.19.0",
|
||||||
|
"autoprefixer": "^10.4.19",
|
||||||
"concurrently": "^8.2.2",
|
"concurrently": "^8.2.2",
|
||||||
|
"daisyui": "^4.12.2",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-import": "^2.29.1",
|
"eslint-plugin-import": "^2.29.1",
|
||||||
|
@ -59,7 +63,10 @@
|
||||||
"eslint-plugin-unused-imports": "^3.2.0",
|
"eslint-plugin-unused-imports": "^3.2.0",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"jest-preset-angular": "^14.0.3",
|
"jest-preset-angular": "^14.0.3",
|
||||||
|
"postcss": "^8.4.38",
|
||||||
"prettier": "3.2.5",
|
"prettier": "3.2.5",
|
||||||
|
"tailwindcss": "^3.4.4",
|
||||||
|
"tailwindcss-animated": "^1.1.2",
|
||||||
"typescript": "~5.4.2",
|
"typescript": "~5.4.2",
|
||||||
"wait-on": "^7.2.0"
|
"wait-on": "^7.2.0"
|
||||||
},
|
},
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,6 @@
|
||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { RouterOutlet } from '@angular/router';
|
import { RouterOutlet } from '@angular/router';
|
||||||
|
|
||||||
|
import { ThemeService } from './shared/service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
|
@ -10,5 +12,5 @@ import { RouterOutlet } from '@angular/router';
|
||||||
styleUrl: './app.component.scss',
|
styleUrl: './app.component.scss',
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
public constructor() {}
|
public constructor(private readonly themeService: ThemeService) {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ const publicRoutes: Routes = [
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'signup',
|
path: 'welcome',
|
||||||
loadComponent: () =>
|
loadComponent: () =>
|
||||||
import('./pages/register-root/register-root.component').then(
|
import('./pages/register-root/register-root.component').then(
|
||||||
(m) => m.RegisterRootComponent
|
(m) => m.RegisterRootComponent
|
||||||
|
|
|
@ -1,20 +1,39 @@
|
||||||
<div id="background">
|
<div class="bg-primary w-screen h-screen">
|
||||||
<div class="wrapper">
|
<div class="modal modal-open">
|
||||||
<div class="content">
|
<div
|
||||||
|
[ngStyle]="backgroundStyle"
|
||||||
|
class="modal-box w-11/12 h-2/6 max-w-5xl flex">
|
||||||
|
<div class="w-full flex flex-col justify-center items-center">
|
||||||
@if (verifyStatus() === true) {
|
@if (verifyStatus() === true) {
|
||||||
@if (showRedirectMessage()) {
|
@if (showRedirectMessage()) {
|
||||||
<h1>Es geht gleich los!</h1>
|
<div class="text-center">
|
||||||
<h2>
|
<h1 class="font-bold text-3xl pt-5">Your email is verified!</h1>
|
||||||
Danke für das bestätigen der E-Mail - Wir leiten dich zum Login
|
<p class="pt-3 pb-6">
|
||||||
weiter!
|
Your email {{ email() }} has been successfully verified. will
|
||||||
</h2>
|
<br />
|
||||||
|
You will be automatically redirected in to the login page to
|
||||||
|
access the application shortly.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
(click)="navigateToWelcomeScreen()"
|
||||||
|
class="btn btn-primary no-animation">
|
||||||
|
Go to the App
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
} @else if (verifyStatus() === false) {
|
} @else if (verifyStatus() === false) {
|
||||||
<h1>Oops, da ist etwas schief gelaufen!</h1>
|
<div class="text-center">
|
||||||
<h2>Der Link ist nicht mehr gültig</h2>
|
<h1 class="font-bold text-3xl pt-5">
|
||||||
|
Oops, something went wrong! :(
|
||||||
|
</h1>
|
||||||
|
<p class="pt-3">We couldn't verify your email.</p>
|
||||||
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
<h1>Verifizierung wird durchgeführt...</h1>
|
<div class="text-center">
|
||||||
|
<span class="loading loading-dots loading-lg"></span>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
#background {
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wrapper {
|
|
||||||
flex: 1;
|
|
||||||
background-color: lightsalmon;
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
.content{
|
|
||||||
flex-direction: column;
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: center;
|
|
||||||
h1 {
|
|
||||||
font-size: 4rem;
|
|
||||||
margin-left: 3rem;
|
|
||||||
line-height: 1rem;
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
margin-left: 3rem;
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
margin-left: 3rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
import {
|
import {
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
Component,
|
Component,
|
||||||
|
ElementRef,
|
||||||
InputSignal,
|
InputSignal,
|
||||||
OnInit,
|
OnInit,
|
||||||
WritableSignal,
|
WritableSignal,
|
||||||
|
@ -12,11 +14,12 @@ import { Router } from '@angular/router';
|
||||||
import { delay, filter, tap } from 'rxjs';
|
import { delay, filter, tap } from 'rxjs';
|
||||||
|
|
||||||
import { VerifyApiService } from '../../api';
|
import { VerifyApiService } from '../../api';
|
||||||
|
import { BackgroundPatternService, ThemeService } from '../../shared/service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-email-verify-root',
|
selector: 'app-email-verify-root',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [],
|
imports: [CommonModule],
|
||||||
providers: [],
|
providers: [],
|
||||||
templateUrl: './email-verify-root.component.html',
|
templateUrl: './email-verify-root.component.html',
|
||||||
styleUrl: './email-verify-root.component.scss',
|
styleUrl: './email-verify-root.component.scss',
|
||||||
|
@ -24,6 +27,8 @@ import { VerifyApiService } from '../../api';
|
||||||
})
|
})
|
||||||
export class EmailVerifyRootComponent implements OnInit {
|
export class EmailVerifyRootComponent implements OnInit {
|
||||||
public token: InputSignal<string> = input<string>('');
|
public token: InputSignal<string> = input<string>('');
|
||||||
|
public email: WritableSignal<string> = signal<string>('');
|
||||||
|
public backgroundStyle: { 'background-image': string } | null = null;
|
||||||
public verifyStatus: WritableSignal<boolean | null> = signal<boolean | null>(
|
public verifyStatus: WritableSignal<boolean | null> = signal<boolean | null>(
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
@ -31,19 +36,71 @@ export class EmailVerifyRootComponent implements OnInit {
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
private readonly api: VerifyApiService,
|
private readonly api: VerifyApiService,
|
||||||
private readonly router: Router
|
private readonly router: Router,
|
||||||
|
private readonly el: ElementRef,
|
||||||
|
private readonly backgroundPatternService: BackgroundPatternService,
|
||||||
|
private readonly themeService: ThemeService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public ngOnInit(): void {
|
public ngOnInit(): void {
|
||||||
this.verifyEmail();
|
this.verifyEmail();
|
||||||
|
this.setBackground();
|
||||||
|
}
|
||||||
|
|
||||||
|
public setBackground(): void {
|
||||||
|
const theme = this.themeService.getTheme();
|
||||||
|
let opacity: number;
|
||||||
|
|
||||||
|
if (theme === 'dark') {
|
||||||
|
opacity = 0.05;
|
||||||
|
} else {
|
||||||
|
opacity = 0.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const colorPrimary = getComputedStyle(
|
||||||
|
this.el.nativeElement
|
||||||
|
).getPropertyValue('--p');
|
||||||
|
|
||||||
|
const svgUrl = this.backgroundPatternService.getWigglePattern(
|
||||||
|
colorPrimary,
|
||||||
|
opacity
|
||||||
|
);
|
||||||
|
|
||||||
|
this.backgroundStyle = { 'background-image': `url("${svgUrl}")` };
|
||||||
|
}
|
||||||
|
|
||||||
|
public navigateToWelcomeScreen(): void {
|
||||||
|
const email: string = this.extractEmail();
|
||||||
|
|
||||||
|
this.router.navigate(['/welcome'], {
|
||||||
|
queryParams: { verified: true, email: email },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private extractVerifyToken(): string {
|
||||||
|
const [verifyToken]: string[] = this.token().split('|');
|
||||||
|
|
||||||
|
return verifyToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
private extractEmail(): string {
|
||||||
|
const [, email]: string[] = this.token().split('|');
|
||||||
|
|
||||||
|
return email;
|
||||||
}
|
}
|
||||||
|
|
||||||
private verifyEmail(): void {
|
private verifyEmail(): void {
|
||||||
const [verifyToken, email]: string[] = this.token().split('|');
|
const verifyToken: string = this.extractVerifyToken();
|
||||||
|
const email: string = this.extractEmail();
|
||||||
|
|
||||||
|
if (verifyToken && email) {
|
||||||
|
this.email.set(decodeURIComponent(atob(email)));
|
||||||
|
}
|
||||||
|
|
||||||
this.api
|
this.api
|
||||||
.verifyControllerVerifyEmail(verifyToken)
|
.verifyControllerVerifyEmail(verifyToken)
|
||||||
.pipe(
|
.pipe(
|
||||||
|
delay(1500),
|
||||||
tap((isVerified: boolean) => {
|
tap((isVerified: boolean) => {
|
||||||
this.verifyStatus.set(isVerified);
|
this.verifyStatus.set(isVerified);
|
||||||
}),
|
}),
|
||||||
|
@ -51,12 +108,10 @@ export class EmailVerifyRootComponent implements OnInit {
|
||||||
tap(() => {
|
tap(() => {
|
||||||
this.showRedirectMessage.set(true);
|
this.showRedirectMessage.set(true);
|
||||||
}),
|
}),
|
||||||
delay(5000)
|
delay(10000)
|
||||||
)
|
)
|
||||||
.subscribe(() => {
|
.subscribe(() => {
|
||||||
this.router.navigate(['/signup'], {
|
this.navigateToWelcomeScreen();
|
||||||
queryParams: { verified: true, email: email },
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ export class HomeComponent implements OnInit {
|
||||||
},
|
},
|
||||||
(error: HttpErrorResponse) => {
|
(error: HttpErrorResponse) => {
|
||||||
if (error.status === 401) {
|
if (error.status === 401) {
|
||||||
this.router.navigate(['signup'], {
|
this.router.navigate(['welcome'], {
|
||||||
queryParams: { login: true },
|
queryParams: { login: true },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,110 +1,301 @@
|
||||||
<div id="background">
|
@if (!userSignupSuccess()) {
|
||||||
<div class="img-zone">
|
<div class="flex h-screen w-screen">
|
||||||
<div class="img-wrapper">
|
<div
|
||||||
@if (userSignupSuccess()) {
|
[ngStyle]="leftBackgroundStyle"
|
||||||
<div class="success">
|
class="hidden md:flex md:flex-col md:w-1/2 bg-primary">
|
||||||
<h1>Danke für deine Registrierung!</h1>
|
<div class="flex-1 flex items-start pt-16 px-12">
|
||||||
<h2>
|
<h1 class="text-3xl text-base-100">[LOGO] APP-NAME</h1>
|
||||||
Wir haben dir eine Mail geschickt an
|
|
||||||
{{ form?.get('email')?.value }}. Bitte bestätige deine
|
|
||||||
E-Mail-Adresse um fortzufahren.
|
|
||||||
</h2>
|
|
||||||
<p>Du kannst diesen Tab nun schließen</p>
|
|
||||||
</div>
|
</div>
|
||||||
} @else {
|
<div class="flex-1 flex flex-col justify-end pb-16 px-12">
|
||||||
<div class="headline">
|
<blockquote>
|
||||||
<h1>Hi, Welcome to Ticket App.</h1>
|
<p class="text-xl text-base-100 font-semibold">
|
||||||
</div>
|
“This library has saved me countless hours of work and helped me
|
||||||
}
|
deliver stunning designs to my clients faster than ever before.”
|
||||||
|
</p>
|
||||||
|
<small class="block text-sm font-light text-base-100 mt-4">
|
||||||
|
— Sofia Davis
|
||||||
|
</small>
|
||||||
|
</blockquote>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (!userSignupSuccess()) {
|
<!-- Rechter Bereich, immer sichtbar -->
|
||||||
<div class="content-zone">
|
<div [ngStyle]="rightBackgroundStyle" class="flex flex-col w-full md:w-1/2">
|
||||||
<h1>
|
<div class="flex px-12 gap-3">
|
||||||
@if (isSignupSignal()) {
|
<div class="flex items-start justify-end pt-16">
|
||||||
Anmelden
|
<label class="swap swap-rotate">
|
||||||
} @else if (isRegisterSignal()) {
|
|
||||||
Registrieren
|
|
||||||
} @else {
|
|
||||||
Erste Schritte
|
|
||||||
}
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
@if (isDisplayButtons()) {
|
|
||||||
<div class="action">
|
|
||||||
<button
|
|
||||||
pButton
|
|
||||||
type="button"
|
|
||||||
label="Anmelden"
|
|
||||||
(click)="toggleAction('signup')"></button>
|
|
||||||
<button
|
|
||||||
pButton
|
|
||||||
type="button"
|
|
||||||
label="Registrieren"
|
|
||||||
(click)="toggleAction('register')"></button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (isSignupSignal() || isRegisterSignal()) {
|
|
||||||
<div class="register-wrapper">
|
|
||||||
@if (form) {
|
|
||||||
<form [formGroup]="form" (ngSubmit)="onSubmit()">
|
|
||||||
<div class="e-mail">
|
|
||||||
<div class="label">
|
|
||||||
<label for="email">E-Mail</label>
|
|
||||||
</div>
|
|
||||||
<input
|
<input
|
||||||
pInputText
|
type="checkbox"
|
||||||
id="email"
|
(change)="toggleTheme()"
|
||||||
formControlName="email"
|
[checked]="isDarkMode" />
|
||||||
aria-describedby="e-mail" />
|
|
||||||
|
<!-- sun icon -->
|
||||||
|
<svg
|
||||||
|
class="swap-on h-10 w-10 fill-current"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
d="M5.64,17l-.71.71a1,1,0,0,0,0,1.41,1,1,0,0,0,1.41,0l.71-.71A1,1,0,0,0,5.64,17ZM5,12a1,1,0,0,0-1-1H3a1,1,0,0,0,0,2H4A1,1,0,0,0,5,12Zm7-7a1,1,0,0,0,1-1V3a1,1,0,0,0-2,0V4A1,1,0,0,0,12,5ZM5.64,7.05a1,1,0,0,0,.7.29,1,1,0,0,0,.71-.29,1,1,0,0,0,0-1.41l-.71-.71A1,1,0,0,0,4.93,6.34Zm12,.29a1,1,0,0,0,.7-.29l.71-.71a1,1,0,1,0-1.41-1.41L17,5.64a1,1,0,0,0,0,1.41A1,1,0,0,0,17.66,7.34ZM21,11H20a1,1,0,0,0,0,2h1a1,1,0,0,0,0-2Zm-9,8a1,1,0,0,0-1,1v1a1,1,0,0,0,2,0V20A1,1,0,0,0,12,19ZM18.36,17A1,1,0,0,0,17,18.36l.71.71a1,1,0,0,0,1.41,0,1,1,0,0,0,0-1.41ZM12,6.5A5.5,5.5,0,1,0,17.5,12,5.51,5.51,0,0,0,12,6.5Zm0,9A3.5,3.5,0,1,1,15.5,12,3.5,3.5,0,0,1,12,15.5Z" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<!-- moon icon -->
|
||||||
|
<svg
|
||||||
|
class="swap-off h-10 w-10 fill-current"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
d="M21.64,13a1,1,0,0,0-1.05-.14,8.05,8.05,0,0,1-3.37.73A8.15,8.15,0,0,1,9.08,5.49a8.59,8.59,0,0,1,.25-2A1,1,0,0,0,8,2.36,10.14,10.14,0,1,0,22,14.05,1,1,0,0,0,21.64,13Zm-9.5,6.69A8.14,8.14,0,0,1,7.08,5.22v.27A10.15,10.15,0,0,0,17.22,15.63a9.79,9.79,0,0,0,2.1-.22A8.11,8.11,0,0,1,12.14,19.73Z" />
|
||||||
|
</svg>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="password">
|
<div class="flex-1 items-start flex justify-end pt-16">
|
||||||
<div class="label">
|
|
||||||
<label for="password">Password</label>
|
|
||||||
</div>
|
|
||||||
<p-password
|
|
||||||
class="custom-p-password"
|
|
||||||
id="password"
|
|
||||||
formControlName="password"
|
|
||||||
aria-describedby="password"
|
|
||||||
[toggleMask]="true"></p-password>
|
|
||||||
</div>
|
|
||||||
@if (isRegisterSignal()) {
|
|
||||||
<div class="terms">
|
|
||||||
<p-checkbox
|
|
||||||
formControlName="terms"
|
|
||||||
label="Ich habe die AGB gelesen und stimme zu."
|
|
||||||
name="terms"
|
|
||||||
[binary]="true"></p-checkbox>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<div class="signup">
|
|
||||||
<button
|
|
||||||
pButton
|
|
||||||
type="submit"
|
|
||||||
[label]="
|
|
||||||
isSignupSignal()
|
|
||||||
? 'Anmelden'
|
|
||||||
: '✨ Jetzt KOSTENFREI loslegen ✨'
|
|
||||||
"></button>
|
|
||||||
</div>
|
|
||||||
<div class="change-mask">
|
|
||||||
<a
|
|
||||||
(click)="switchMask()"
|
|
||||||
(keyup.enter)="switchMask()"
|
|
||||||
tabindex="0">
|
|
||||||
@if (isSignupSignal()) {
|
@if (isSignupSignal()) {
|
||||||
Kein Account? Erstellen Sie jetzt KOSTENFREI einen!
|
<button
|
||||||
} @else {
|
(click)="toggleAction('signin')"
|
||||||
Schon einen Account? Hier einloggen
|
class="btn btn-primary btn-outline no-animation">
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
}
|
}
|
||||||
|
@if (isSigninSignal()) {
|
||||||
|
@if (displaySkeleton()) {
|
||||||
|
<div class="skeleton w-36 h-12"></div>
|
||||||
|
} @else {
|
||||||
|
<button
|
||||||
|
(click)="toggleAction('signup')"
|
||||||
|
class="btn btn-primary btn-outline no-animation">
|
||||||
|
New here - Register now!
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (isSignupSignal()) {
|
||||||
|
<div
|
||||||
|
class="animate-fade-down animate-once animate-duration-1000 animate-ease-in-out flex-1 flex flex-col justify-center items-center px-12">
|
||||||
|
<h1 class="text-3xl font-semibold text-center">Create an Account</h1>
|
||||||
|
<p class="text-center">
|
||||||
|
Enter your email below to create your Account
|
||||||
|
</p>
|
||||||
|
<form
|
||||||
|
[formGroup]="form"
|
||||||
|
(ngSubmit)="onSubmit()"
|
||||||
|
class="flex gap-4 flex-col items-center py-6 w-full max-w-md">
|
||||||
|
<div class="form-control w-full">
|
||||||
|
<label
|
||||||
|
[ngClass]="{
|
||||||
|
'w-full': true,
|
||||||
|
'border-error focus:border-error':
|
||||||
|
form.get('email')?.invalid &&
|
||||||
|
(form.get('email')?.dirty || form.get('email')?.touched)
|
||||||
|
}"
|
||||||
|
class="input input-bordered flex items-center gap-2">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
fill="currentColor"
|
||||||
|
class="h-4 w-4 opacity-70">
|
||||||
|
<path
|
||||||
|
d="M2.5 3A1.5 1.5 0 0 0 1 4.5v.793c.026.009.051.02.076.032L7.674 8.51c.206.1.446.1.652 0l6.598-3.185A.755.755 0 0 1 15 5.293V4.5A1.5 1.5 0 0 0 13.5 3h-11Z" />
|
||||||
|
<path
|
||||||
|
d="M15 6.954 8.978 9.86a2.25 2.25 0 0 1-1.956 0L1 6.954V11.5A1.5 1.5 0 0 0 2.5 13h11a1.5 1.5 0 0 0 1.5-1.5V6.954Z" />
|
||||||
|
</svg>
|
||||||
|
<input
|
||||||
|
formControlName="email"
|
||||||
|
type="text"
|
||||||
|
class="grow"
|
||||||
|
placeholder="name@example.com" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-control w-full">
|
||||||
|
<label
|
||||||
|
[ngClass]="{
|
||||||
|
'w-full': true,
|
||||||
|
'border-error focus:border-error':
|
||||||
|
form.get('password')?.invalid &&
|
||||||
|
(form.get('password')?.dirty ||
|
||||||
|
form.get('password')?.touched)
|
||||||
|
}"
|
||||||
|
class="input input-bordered flex items-center gap-2">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
fill="currentColor"
|
||||||
|
class="h-4 w-4 opacity-70">
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M14 6a4 4 0 0 1-4.899 3.899l-1.955 1.955a.5.5 0 0 1-.353.146H5v1.5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1-.5-.5v-2.293a.5.5 0 0 1 .146-.353l3.955-3.955A4 4 0 1 1 14 6Zm-4-2a.75.75 0 0 0 0 1.5.5.5 0 0 1 .5.5.75.75 0 0 0 1.5 0 2 2 0 0 0-2-2Z"
|
||||||
|
clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
<input
|
||||||
|
formControlName="password"
|
||||||
|
type="password"
|
||||||
|
class="grow"
|
||||||
|
value="" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<button class="btn w-full btn-primary font-semibold">
|
||||||
|
@if (isLoading()) {
|
||||||
|
<span class="loading loading-spinner"></span>
|
||||||
|
}
|
||||||
|
Sign Up with Email
|
||||||
|
</button>
|
||||||
|
<p class="text-xs w-full text-center">
|
||||||
|
By clicking continue, you agree to our
|
||||||
|
<u class="cursor-pointer">Terms of Service</u>
|
||||||
|
and
|
||||||
|
<u class="cursor-pointer">Privacy Policy</u>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (isSigninSignal()) {
|
||||||
|
<div
|
||||||
|
class="animate-fade-down animate-once animate-duration-1000 animate-ease-in-out flex-1 flex flex-col justify-center items-center px-12">
|
||||||
|
@if (displaySkeleton()) {
|
||||||
|
<div class="flex items-center w-full flex-col max-w-md gap-4">
|
||||||
|
<div class="skeleton w-36 h-10"></div>
|
||||||
|
<div class="skeleton w-full h-10 max-w-md"></div>
|
||||||
|
<div class="skeleton w-full h-10 max-w-md"></div>
|
||||||
|
<div class="skeleton w-full h-10 max-w-md"></div>
|
||||||
|
<div class="skeleton w-full h-10 max-w-md"></div>
|
||||||
|
</div>
|
||||||
|
} @else {
|
||||||
|
<h1 class="text-3xl font-semibold text-center">Login</h1>
|
||||||
|
<form
|
||||||
|
[formGroup]="form"
|
||||||
|
(ngSubmit)="onSubmit()"
|
||||||
|
class="flex gap-4 flex-col items-center py-6 w-full max-w-md">
|
||||||
|
<div class="form-control w-full">
|
||||||
|
<label
|
||||||
|
[ngClass]="{
|
||||||
|
'w-full': true,
|
||||||
|
'border-error focus:border-error':
|
||||||
|
form.get('email')?.invalid &&
|
||||||
|
(form.get('email')?.dirty || form.get('email')?.touched)
|
||||||
|
}"
|
||||||
|
class="input input-bordered flex items-center gap-2">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
fill="currentColor"
|
||||||
|
class="h-4 w-4 opacity-70">
|
||||||
|
<path
|
||||||
|
d="M2.5 3A1.5 1.5 0 0 0 1 4.5v.793c.026.009.051.02.076.032L7.674 8.51c.206.1.446.1.652 0l6.598-3.185A.755.755 0 0 1 15 5.293V4.5A1.5 1.5 0 0 0 13.5 3h-11Z" />
|
||||||
|
<path
|
||||||
|
d="M15 6.954 8.978 9.86a2.25 2.25 0 0 1-1.956 0L1 6.954V11.5A1.5 1.5 0 0 0 2.5 13h11a1.5 1.5 0 0 0 1.5-1.5V6.954Z" />
|
||||||
|
</svg>
|
||||||
|
<input
|
||||||
|
formControlName="email"
|
||||||
|
type="text"
|
||||||
|
class="grow"
|
||||||
|
placeholder="name@example.com" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-control w-full">
|
||||||
|
<label
|
||||||
|
[ngClass]="{
|
||||||
|
'w-full': true,
|
||||||
|
'border-error focus:border-error':
|
||||||
|
form.get('password')?.invalid &&
|
||||||
|
(form.get('password')?.dirty ||
|
||||||
|
form.get('password')?.touched)
|
||||||
|
}"
|
||||||
|
class="input input-bordered flex items-center gap-2">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
fill="currentColor"
|
||||||
|
class="h-4 w-4 opacity-70">
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M14 6a4 4 0 0 1-4.899 3.899l-1.955 1.955a.5.5 0 0 1-.353.146H5v1.5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1-.5-.5v-2.293a.5.5 0 0 1 .146-.353l3.955-3.955A4 4 0 1 1 14 6Zm-4-2a.75.75 0 0 0 0 1.5.5.5 0 0 1 .5.5.75.75 0 0 0 1.5 0 2 2 0 0 0-2-2Z"
|
||||||
|
clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
<input
|
||||||
|
formControlName="password"
|
||||||
|
type="password"
|
||||||
|
class="grow"
|
||||||
|
value="" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-control w-full">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<label class="label cursor-pointer">
|
||||||
|
<input
|
||||||
|
[formControl]="rememberMe"
|
||||||
|
type="checkbox"
|
||||||
|
checked="checked"
|
||||||
|
class="checkbox checkbox-md checkbox-primary" />
|
||||||
|
<span class="label-text ml-1.5">Remember me</span>
|
||||||
|
</label>
|
||||||
|
<a class="text-primary label-text cursor-pointer">
|
||||||
|
Forgot password?
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="btn w-full btn-primary font-semibold">
|
||||||
|
@if (isLoading()) {
|
||||||
|
<span class="loading loading-spinner"></span>
|
||||||
|
}
|
||||||
|
Sign In
|
||||||
|
</button>
|
||||||
|
<div class="flex gap-1">
|
||||||
|
<span class="text-xs">Not registered yet?</span>
|
||||||
|
<a
|
||||||
|
(click)="toggleAction('signup')"
|
||||||
|
(keypress)="toggleAction('signup')"
|
||||||
|
tabindex="0"
|
||||||
|
class="text-primary cursor-pointer text-xs">
|
||||||
|
Create An Account
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
<div class="flex flex-col items-center justify-center py-12">
|
||||||
|
<footer>
|
||||||
|
<p class="text-xs">Made with ♥️ in Germany</p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
} @else {
|
||||||
|
<div class="flex h-screen w-screen bg-primary">
|
||||||
|
<div class="hidden md:flex md:flex-col md:w-1/1"></div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="modal modal-open" *ngIf="isDialogOpen()">
|
||||||
|
<div
|
||||||
|
[ngStyle]="dialogBackgroundStyle"
|
||||||
|
class="modal-box w-11/12 h-2/6 max-w-5xl flex">
|
||||||
|
<div class="w-full flex flex-col justify-center items-center">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1"
|
||||||
|
stroke="currentColor"
|
||||||
|
class="size-28 animate-jump animate-once animate-duration-[2000ms] animate-delay-500 animate-ease-in-out animate-normal">
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M21.75 9v.906a2.25 2.25 0 0 1-1.183 1.981l-6.478 3.488M2.25 9v.906a2.25 2.25 0 0 0 1.183 1.981l6.478 3.488m8.839 2.51-4.66-2.51m0 0-1.023-.55a2.25 2.25 0 0 0-2.134 0l-1.022.55m0 0-4.661 2.51m16.5 1.615a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V8.844a2.25 2.25 0 0 1 1.183-1.981l7.5-4.039a2.25 2.25 0 0 1 2.134 0l7.5 4.039a2.25 2.25 0 0 1 1.183 1.98V19.5Z" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<h1 class="font-bold text-3xl pt-5">Check your inbox, please!</h1>
|
||||||
|
<div class="flex flex-col items-center text-center">
|
||||||
|
<p class="pt-3">
|
||||||
|
Hey, to start using [APP-NAME], we need to verify your email.
|
||||||
|
</p>
|
||||||
|
<p class="pt-1">
|
||||||
|
We´ve already sent out the verification link. Please check it and
|
||||||
|
<br />
|
||||||
|
confirm it´s really you.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,104 +0,0 @@
|
||||||
#background {
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.img-zone {
|
|
||||||
flex: 65;
|
|
||||||
background-color: lightsalmon;
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
.img-wrapper {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.success {
|
|
||||||
margin-left: 4em;
|
|
||||||
h1 {
|
|
||||||
font-size: 4em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.headline {
|
|
||||||
h1 {
|
|
||||||
font-size: 4em;
|
|
||||||
margin-left: 1em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-zone {
|
|
||||||
flex: 35;
|
|
||||||
background-color: lightcyan;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.action {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 1em;
|
|
||||||
|
|
||||||
button {
|
|
||||||
min-width: 200px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.register-wrapper {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
padding: 0 0 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.e-mail,
|
|
||||||
.password,
|
|
||||||
.terms {
|
|
||||||
.label {
|
|
||||||
font-size: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
min-width: 500px;
|
|
||||||
}
|
|
||||||
::ng-deep p-password.custom-p-password div input {
|
|
||||||
min-width: 500px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.terms {
|
|
||||||
padding-top: 1.33em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.password {
|
|
||||||
padding-top: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signup {
|
|
||||||
padding-top: 3em;
|
|
||||||
|
|
||||||
button {
|
|
||||||
min-width: 500px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.change-mask {
|
|
||||||
padding-top: 1.33em;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
a {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
effect,
|
effect,
|
||||||
InputSignal,
|
InputSignal,
|
||||||
input,
|
input,
|
||||||
|
ElementRef,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import {
|
import {
|
||||||
FormBuilder,
|
FormBuilder,
|
||||||
|
@ -24,6 +25,7 @@ import { ButtonModule } from 'primeng/button';
|
||||||
import { CheckboxModule } from 'primeng/checkbox';
|
import { CheckboxModule } from 'primeng/checkbox';
|
||||||
import { InputTextModule } from 'primeng/inputtext';
|
import { InputTextModule } from 'primeng/inputtext';
|
||||||
import { PasswordModule } from 'primeng/password';
|
import { PasswordModule } from 'primeng/password';
|
||||||
|
import { delay, finalize, tap } from 'rxjs';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Configuration,
|
Configuration,
|
||||||
|
@ -32,13 +34,18 @@ import {
|
||||||
UserCredentialsDtoApiModel,
|
UserCredentialsDtoApiModel,
|
||||||
} from '../../api';
|
} from '../../api';
|
||||||
import { ApiConfiguration } from '../../config/api-configuration';
|
import { ApiConfiguration } from '../../config/api-configuration';
|
||||||
import { AuthService, SessionStorageService } from '../../shared/service';
|
import {
|
||||||
|
AuthService,
|
||||||
|
BackgroundPatternService,
|
||||||
|
ThemeService,
|
||||||
|
} from '../../shared/service';
|
||||||
|
import { LocalStorageService } from '../../shared/service/local-storage.service';
|
||||||
import {
|
import {
|
||||||
customEmailValidator,
|
customEmailValidator,
|
||||||
customPasswordValidator,
|
customPasswordValidator,
|
||||||
} from '../../shared/validator';
|
} from '../../shared/validator';
|
||||||
|
|
||||||
type AuthAction = 'register' | 'signup';
|
type AuthAction = 'signin' | 'signup';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-register-root',
|
selector: 'app-register-root',
|
||||||
|
@ -65,47 +72,68 @@ type AuthAction = 'register' | 'signup';
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class RegisterRootComponent implements OnInit {
|
export class RegisterRootComponent implements OnInit {
|
||||||
|
public dialogBackgroundStyle: { 'background-image': string } | null = null;
|
||||||
|
public leftBackgroundStyle: { 'background-image': string } | null = null;
|
||||||
|
public rightBackgroundStyle: { 'background-image': string } | null = null;
|
||||||
public verified: InputSignal<boolean> = input<boolean>(false);
|
public verified: InputSignal<boolean> = input<boolean>(false);
|
||||||
public login: InputSignal<boolean> = input<boolean>(false);
|
public login: InputSignal<boolean> = input<boolean>(false);
|
||||||
public email: InputSignal<string> = input<string>('');
|
public email: InputSignal<string> = input<string>('');
|
||||||
public form: FormGroup | undefined;
|
public form!: FormGroup;
|
||||||
public isRegisterSignal: WritableSignal<boolean> = signal(false);
|
public rememberMe: FormControl = new FormControl(false);
|
||||||
public isSignupSignal: WritableSignal<boolean> = signal(false);
|
public isSigninSignal: WritableSignal<boolean> = signal(false);
|
||||||
public isDisplayButtons: WritableSignal<boolean> = signal(true);
|
public isSignupSignal: WritableSignal<boolean> = signal(true);
|
||||||
public emailInvalid: WritableSignal<string | null> = signal(null);
|
public isSignUpSuccess: WritableSignal<boolean> = signal(false);
|
||||||
public passwordInvalid: WritableSignal<string | null> = signal(null);
|
|
||||||
public termsInvalid: WritableSignal<string | null> = signal(null);
|
|
||||||
public userSignupSuccess: WritableSignal<boolean> = signal(false);
|
public userSignupSuccess: WritableSignal<boolean> = signal(false);
|
||||||
|
public isDialogOpen: WritableSignal<boolean> = signal(false);
|
||||||
|
public isLoading: WritableSignal<boolean> = signal(false);
|
||||||
|
public displaySkeleton: WritableSignal<boolean> = signal(true);
|
||||||
private removeQueryParams: WritableSignal<boolean> = signal(false);
|
private removeQueryParams: WritableSignal<boolean> = signal(false);
|
||||||
|
|
||||||
|
public get isDarkMode(): boolean {
|
||||||
|
return this.themeService.getTheme() === 'dark';
|
||||||
|
}
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
private readonly formBuilder: FormBuilder,
|
private readonly formBuilder: FormBuilder,
|
||||||
private readonly authService: AuthService,
|
private readonly authService: AuthService,
|
||||||
private readonly router: Router,
|
private readonly router: Router,
|
||||||
private readonly sessionStorageService: SessionStorageService
|
private readonly themeService: ThemeService,
|
||||||
|
private readonly el: ElementRef,
|
||||||
|
private readonly backgroundPatternService: BackgroundPatternService,
|
||||||
|
private readonly localStorageService: LocalStorageService
|
||||||
) {
|
) {
|
||||||
effect(() => {
|
effect(() => {
|
||||||
if (this.form) {
|
|
||||||
if (this.isRegisterSignal()) {
|
|
||||||
this.form.addControl(
|
|
||||||
'terms',
|
|
||||||
new FormControl(false, Validators.requiredTrue)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this.form.removeControl('terms');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.removeQueryParams()) {
|
if (this.removeQueryParams()) {
|
||||||
this.clearRouteParams();
|
this.clearRouteParams();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const rememberMe = this.localStorageService.getItem<boolean>('remember-me');
|
||||||
|
|
||||||
|
if (rememberMe) {
|
||||||
|
this.authService
|
||||||
|
.status()
|
||||||
|
.pipe(delay(1500))
|
||||||
|
.subscribe({
|
||||||
|
next: (response: SuccessDtoApiModel) => {
|
||||||
|
if (response.success) {
|
||||||
|
this.router.navigate(['/dashboard']);
|
||||||
|
} else {
|
||||||
|
this.displaySkeleton.set(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
this.displaySkeleton.set(false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.displaySkeleton.set(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ngOnInit(): void {
|
public ngOnInit(): void {
|
||||||
|
this.setBackground();
|
||||||
this.initializeForm();
|
this.initializeForm();
|
||||||
this.setupValueChanges();
|
this.setupValueChanges();
|
||||||
this.preselectForm();
|
|
||||||
|
|
||||||
if ((this.email() && this.verified()) || this.login()) {
|
if ((this.email() && this.verified()) || this.login()) {
|
||||||
this.handleRedirect();
|
this.handleRedirect();
|
||||||
|
@ -113,52 +141,116 @@ export class RegisterRootComponent implements OnInit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public preselectForm(): void {
|
public setBackground(): void {
|
||||||
if (!this.email() || !this.verified()) {
|
const theme = this.themeService.getTheme();
|
||||||
const email = this.sessionStorageService.getItem('email');
|
let opacity: number;
|
||||||
|
|
||||||
this.form?.get('email')?.setValue(email);
|
if (theme === 'dark') {
|
||||||
|
opacity = 0.05;
|
||||||
|
} else {
|
||||||
|
opacity = 0.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const colorPrimary = getComputedStyle(
|
||||||
|
this.el.nativeElement
|
||||||
|
).getPropertyValue('--p');
|
||||||
|
|
||||||
|
const colorPrimaryC = getComputedStyle(
|
||||||
|
this.el.nativeElement
|
||||||
|
).getPropertyValue('--pc');
|
||||||
|
|
||||||
|
const svgUrlforDialog = this.backgroundPatternService.getWigglePattern(
|
||||||
|
colorPrimary,
|
||||||
|
opacity
|
||||||
|
);
|
||||||
|
const svgUrlForLeft = this.backgroundPatternService.getBankNotePattern(
|
||||||
|
colorPrimaryC,
|
||||||
|
opacity
|
||||||
|
);
|
||||||
|
const svgUrlForRight = this.backgroundPatternService.getHideoutPattern(
|
||||||
|
colorPrimary,
|
||||||
|
opacity
|
||||||
|
);
|
||||||
|
|
||||||
|
this.dialogBackgroundStyle = {
|
||||||
|
'background-image': `url("${svgUrlforDialog}")`,
|
||||||
|
};
|
||||||
|
this.leftBackgroundStyle = {
|
||||||
|
'background-image': `url("${svgUrlForLeft}")`,
|
||||||
|
};
|
||||||
|
this.rightBackgroundStyle = {
|
||||||
|
'background-image': `url("${svgUrlForRight}")`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public openModal(): void {
|
||||||
|
this.isDialogOpen.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public closeModal(): void {
|
||||||
|
this.isDialogOpen.set(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public toggleTheme(): void {
|
||||||
|
this.themeService.toggleTheme();
|
||||||
|
this.setBackground();
|
||||||
}
|
}
|
||||||
|
|
||||||
public toggleAction(action: AuthAction): void {
|
public toggleAction(action: AuthAction): void {
|
||||||
if (action === 'register') {
|
this.resetFormValidation();
|
||||||
this.isRegisterSignal.set(true);
|
|
||||||
|
if (action === 'signin') {
|
||||||
|
this.handlePreselect();
|
||||||
|
this.isSigninSignal.set(true);
|
||||||
this.isSignupSignal.set(false);
|
this.isSignupSignal.set(false);
|
||||||
} else {
|
} else {
|
||||||
this.isRegisterSignal.set(false);
|
this.isSigninSignal.set(false);
|
||||||
this.isSignupSignal.set(true);
|
this.isSignupSignal.set(true);
|
||||||
}
|
}
|
||||||
this.isDisplayButtons.set(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public onSubmit(): void {
|
public onSubmit(): void {
|
||||||
this.markControlsAsTouchedAndDirty(['email', 'password', 'terms']);
|
this.markControlsAsTouchedAndDirty(['email', 'password']);
|
||||||
|
|
||||||
if (this.form?.valid) {
|
if (this.form?.valid) {
|
||||||
if (this.isRegisterSignal()) {
|
if (this.isSigninSignal()) {
|
||||||
this.signup(this.form.value);
|
|
||||||
} else {
|
|
||||||
this.signin(this.form.value);
|
this.signin(this.form.value);
|
||||||
|
} else {
|
||||||
|
this.signup(this.form.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public switchMask(): void {
|
private handlePreselect(): void {
|
||||||
this.resetFormValidation();
|
const rememberMe = this.localStorageService.getItem<boolean>('remember-me');
|
||||||
|
const email = this.localStorageService.getItem<string>('email');
|
||||||
|
|
||||||
if (this.isSignupSignal()) {
|
if (rememberMe) {
|
||||||
|
this.isSigninSignal.set(true);
|
||||||
this.isSignupSignal.set(false);
|
this.isSignupSignal.set(false);
|
||||||
this.isRegisterSignal.set(true);
|
|
||||||
} else if (this.isRegisterSignal()) {
|
|
||||||
this.isSignupSignal.set(true);
|
|
||||||
this.isRegisterSignal.set(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (email) {
|
||||||
|
this.form?.get('email')?.setValue(email);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.rememberMe.setValue(rememberMe);
|
||||||
}
|
}
|
||||||
|
|
||||||
private initializeForm(): void {
|
private initializeForm(): void {
|
||||||
|
const rememberMeValue =
|
||||||
|
this.localStorageService.getItem<boolean>('remember-me');
|
||||||
|
const email = this.localStorageService.getItem<string>('email');
|
||||||
|
|
||||||
|
if (rememberMeValue) {
|
||||||
|
this.isSigninSignal.set(true);
|
||||||
|
this.isSignupSignal.set(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const emailValue = rememberMeValue && email ? email : '';
|
||||||
|
|
||||||
this.form = this.formBuilder.group({
|
this.form = this.formBuilder.group({
|
||||||
email: new FormControl('', {
|
email: new FormControl(emailValue, {
|
||||||
validators: [Validators.required, customEmailValidator()],
|
validators: [Validators.required, customEmailValidator()],
|
||||||
updateOn: 'change',
|
updateOn: 'change',
|
||||||
}),
|
}),
|
||||||
|
@ -166,18 +258,15 @@ export class RegisterRootComponent implements OnInit {
|
||||||
validators: [Validators.required, customPasswordValidator()],
|
validators: [Validators.required, customPasswordValidator()],
|
||||||
updateOn: 'change',
|
updateOn: 'change',
|
||||||
}),
|
}),
|
||||||
terms: new FormControl(false, {
|
|
||||||
validators: [Validators.requiredTrue],
|
|
||||||
updateOn: 'change',
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.rememberMe.setValue(rememberMeValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleRedirect(): void {
|
private handleRedirect(): void {
|
||||||
if (this.verified()) {
|
if (this.verified()) {
|
||||||
this.isDisplayButtons.set(false);
|
this.isSigninSignal.set(true);
|
||||||
this.isRegisterSignal.set(false);
|
this.isSignupSignal.set(false);
|
||||||
this.isSignupSignal.set(true);
|
|
||||||
}
|
}
|
||||||
if (this.email()) {
|
if (this.email()) {
|
||||||
this.form?.get('email')?.setValue(decodeURIComponent(atob(this.email())));
|
this.form?.get('email')?.setValue(decodeURIComponent(atob(this.email())));
|
||||||
|
@ -185,8 +274,7 @@ export class RegisterRootComponent implements OnInit {
|
||||||
|
|
||||||
if (this.login()) {
|
if (this.login()) {
|
||||||
this.isSignupSignal.set(true);
|
this.isSignupSignal.set(true);
|
||||||
this.isDisplayButtons.set(false);
|
this.isSigninSignal.set(false);
|
||||||
this.isRegisterSignal.set(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,7 +338,7 @@ export class RegisterRootComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
private resetFormValidation(): void {
|
private resetFormValidation(): void {
|
||||||
['email', 'password', 'terms'].forEach((controlName: string) => {
|
['email', 'password'].forEach((controlName: string) => {
|
||||||
this.resetControlValidation(controlName);
|
this.resetControlValidation(controlName);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -267,8 +355,20 @@ export class RegisterRootComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
private signin(logiCredentials: UserCredentialsDtoApiModel): void {
|
private signin(logiCredentials: UserCredentialsDtoApiModel): void {
|
||||||
|
const rememberMe: boolean = this.rememberMe.value;
|
||||||
|
|
||||||
|
if (rememberMe) {
|
||||||
|
this.localStorageService.setItem<string>('email', logiCredentials.email);
|
||||||
|
this.localStorageService.setItem<boolean>('remember-me', rememberMe);
|
||||||
|
}
|
||||||
|
|
||||||
this.authService
|
this.authService
|
||||||
.signin(logiCredentials)
|
.signin(logiCredentials)
|
||||||
|
.pipe(
|
||||||
|
tap(() => this.isLoading.set(true)),
|
||||||
|
delay(1000),
|
||||||
|
finalize(() => this.isLoading.set(false))
|
||||||
|
)
|
||||||
.subscribe((response: SigninResponseDtoApiModel) => {
|
.subscribe((response: SigninResponseDtoApiModel) => {
|
||||||
if (response) {
|
if (response) {
|
||||||
this.router.navigate(['/dashboard']);
|
this.router.navigate(['/dashboard']);
|
||||||
|
@ -277,10 +377,17 @@ export class RegisterRootComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
private signup(logiCredentials: UserCredentialsDtoApiModel): void {
|
private signup(logiCredentials: UserCredentialsDtoApiModel): void {
|
||||||
|
this.isLoading.set(true);
|
||||||
this.authService
|
this.authService
|
||||||
.signup(logiCredentials)
|
.signup(logiCredentials)
|
||||||
|
.pipe(
|
||||||
|
delay(1000),
|
||||||
|
tap(() => this.isLoading.set(true)),
|
||||||
|
finalize(() => this.isLoading.set(false))
|
||||||
|
)
|
||||||
.subscribe((response: SuccessDtoApiModel) => {
|
.subscribe((response: SuccessDtoApiModel) => {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
|
this.openModal();
|
||||||
this.userSignupSuccess.set(true);
|
this.userSignupSuccess.set(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,137 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
import chroma from 'chroma-js';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class BackgroundPatternService {
|
||||||
|
public getWigglePattern(color: string, opacity: number): string {
|
||||||
|
const colorHex = this.convertOklchStringToHex(color);
|
||||||
|
const encodedHex = encodeURIComponent(colorHex);
|
||||||
|
const pattern = `
|
||||||
|
data:image/svg+xml,
|
||||||
|
%3Csvg width='52' height='26' viewBox='0 0 52 26' xmlns='http://www.w3.org/2000/svg'%3E
|
||||||
|
%3Cg fill='none' fill-rule='evenodd'%3E
|
||||||
|
%3Cg fill='${encodedHex}' fill-opacity='${opacity}'%3E
|
||||||
|
%3Cpath d='M10 10c0-2.21-1.79-4-4-4-3.314 0-6-2.686-6-6h2c0 2.21 1.79 4 4 4 3.314 0 6 2.686 6 6 0 2.21 1.79 4 4 4 3.314 0 6 2.686 6 6 0 2.21 1.79 4 4 4v2c-3.314 0-6-2.686-6-6 0-2.21-1.79-4-4-4-3.314 0-6-2.686-6-6zm25.464-1.95l8.486 8.486-1.414 1.414-8.486-8.486 1.414-1.414z' /%3E
|
||||||
|
%3C/g%3E
|
||||||
|
%3C/g%3E
|
||||||
|
%3C/svg%3E`;
|
||||||
|
|
||||||
|
return pattern.replace(/[\n\r]+|[\s]{2,}/g, ' ').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTexturePattern(color: string, opacity: number): string {
|
||||||
|
const colorHex = this.convertOklchStringToHex(color);
|
||||||
|
const encodedHex = encodeURIComponent(colorHex);
|
||||||
|
const pattern = `
|
||||||
|
data:image/svg+xml,
|
||||||
|
%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3E
|
||||||
|
%3Cpath fill='${encodedHex}' fill-opacity='${opacity}' d='M1 3h1v1H1V3zm2-2h1v1H3V1z'%3E
|
||||||
|
%3C/path%3E
|
||||||
|
%3C/svg%3E`;
|
||||||
|
|
||||||
|
return pattern.replace(/[\n\r]+|[\s]{2,}/g, ' ').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getBubblesPattern(color: string, opacity: number): string {
|
||||||
|
const colorHex = this.convertOklchStringToHex(color);
|
||||||
|
const encodedHex = encodeURIComponent(colorHex);
|
||||||
|
const pattern = `
|
||||||
|
data:image/svg+xml,
|
||||||
|
%3Csvg width='100' height='100' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'%3E
|
||||||
|
%3Cpath d='M11 18c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm48 25c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm-43-7c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm63 31c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM34 90c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm56-76c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM12 86c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm28-65c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm23-11c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-6 60c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm29 22c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zM32 63c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm57-13c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-9-21c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM60 91c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM35 41c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM12 60c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2z' fill='${encodedHex}' fill-opacity='${opacity}' fill-rule='evenodd'/%3E
|
||||||
|
%3C/svg%3E`;
|
||||||
|
|
||||||
|
return pattern.replace(/[\n\r]+|[\s]{2,}/g, ' ').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getBankNotePattern(color: string, opacity: number): string {
|
||||||
|
const colorHex = this.convertOklchStringToHex(color);
|
||||||
|
const encodedHex = encodeURIComponent(colorHex);
|
||||||
|
const pattern = `
|
||||||
|
data:image/svg+xml,
|
||||||
|
%3Csvg width='100' height='20' viewBox='0 0 100 20' xmlns='http://www.w3.org/2000/svg'%3E
|
||||||
|
%3Cpath d='M21.184 20c.357-.13.72-.264 1.088-.402l1.768-.661C33.64 15.347 39.647 14 50 14c10.271 0 15.362 1.222 24.629 4.928.955.383 1.869.74 2.75 1.072h6.225c-2.51-.73-5.139-1.691-8.233-2.928C65.888 13.278 60.562 12 50 12c-10.626 0-16.855 1.397-26.66 5.063l-1.767.662c-2.475.923-4.66 1.674-6.724 2.275h6.335zm0-20C13.258 2.892 8.077 4 0 4V2c5.744 0 9.951-.574 14.85-2h6.334zM77.38 0C85.239 2.966 90.502 4 100 4V2c-6.842 0-11.386-.542-16.396-2h-6.225zM0 14c8.44 0 13.718-1.21 22.272-4.402l1.768-.661C33.64 5.347 39.647 4 50 4c10.271 0 15.362 1.222 24.629 4.928C84.112 12.722 89.438 14 100 14v-2c-10.271 0-15.362-1.222-24.629-4.928C65.888 3.278 60.562 2 50 2 39.374 2 33.145 3.397 23.34 7.063l-1.767.662C13.223 10.84 8.163 12 0 12v2z' fill='${encodedHex}' fill-opacity='${opacity}' fill-rule='evenodd'/%3E
|
||||||
|
%3C/svg%3E`;
|
||||||
|
|
||||||
|
return pattern.replace(/[\n\r]+|[\s]{2,}/g, ' ').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getHideoutPattern(color: string, opacity: number): string {
|
||||||
|
const colorHex = this.convertOklchStringToHex(color);
|
||||||
|
const encodedHex = encodeURIComponent(colorHex);
|
||||||
|
const pattern = `
|
||||||
|
data:image/svg+xml,
|
||||||
|
%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='40' viewBox='0 0 40 40'%3E
|
||||||
|
%3Cg fill-rule='evenodd'%3E
|
||||||
|
%3Cg fill='${encodedHex}' fill-opacity='${opacity}'%3E
|
||||||
|
%3Cpath d='M0 38.59l2.83-2.83 1.41 1.41L1.41 40H0v-1.41zM0 1.4l2.83 2.83 1.41-1.41L1.41 0H0v1.41zM38.59 40l-2.83-2.83 1.41-1.41L40 38.59V40h-1.41zM40 1.41l-2.83 2.83-1.41-1.41L38.59 0H40v1.41zM20 18.6l2.83-2.83 1.41 1.41L21.41 20l2.83 2.83-1.41 1.41L20 21.41l-2.83 2.83-1.41-1.41L18.59 20l-2.83-2.83 1.41-1.41L20 18.59z'/%3E
|
||||||
|
%3C/g%3E
|
||||||
|
%3C/g%3E
|
||||||
|
%3C/svg%3E`;
|
||||||
|
|
||||||
|
return pattern.replace(/[\n\r]+|[\s]{2,}/g, ' ').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getFourPointsStarsPattern(color: string, opacity: number): string {
|
||||||
|
const colorHex = this.convertOklchStringToHex(color);
|
||||||
|
const encodedHex = encodeURIComponent(colorHex);
|
||||||
|
const pattern = `
|
||||||
|
data:image/svg+xml,
|
||||||
|
%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E
|
||||||
|
%3Cg fill='${encodedHex}' fill-opacity='${opacity}'%3E
|
||||||
|
%3Cpolygon fill-rule='evenodd' points='8 4 12 6 8 8 6 12 4 8 0 6 4 4 6 0 8 4'/%3E
|
||||||
|
%3C/g%3E
|
||||||
|
%3C/svg%3E`;
|
||||||
|
|
||||||
|
return pattern.replace(/[\n\r]+|[\s]{2,}/g, ' ').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPlusPattern(color: string, opacity: number): string {
|
||||||
|
const colorHex = this.convertOklchStringToHex(color);
|
||||||
|
const encodedHex = encodeURIComponent(colorHex);
|
||||||
|
const pattern = `
|
||||||
|
data:image/svg+xml,
|
||||||
|
%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E
|
||||||
|
%3Cg fill='none' fill-rule='evenodd'%3E
|
||||||
|
%3Cg fill='${encodedHex}' fill-opacity='${opacity}'%3E
|
||||||
|
%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E
|
||||||
|
%3C/g%3E
|
||||||
|
%3C/g%3E
|
||||||
|
%3C/svg%3E`;
|
||||||
|
|
||||||
|
return pattern.replace(/[\n\r]+|[\s]{2,}/g, ' ').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPolkaDotsPattern(color: string, opacity: number = 0.4): string {
|
||||||
|
const colorHex = this.convertOklchStringToHex(color);
|
||||||
|
const encodedHex = encodeURIComponent(colorHex);
|
||||||
|
const pattern = `
|
||||||
|
data:image/svg+xml,
|
||||||
|
%3Csvg width='20' height='20' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E
|
||||||
|
%3Cg fill='${encodedHex}' fill-opacity='${opacity}' fill-rule='evenodd'%3E
|
||||||
|
%3Ccircle cx='3' cy='3' r='3'/%3E
|
||||||
|
%3Ccircle cx='13' cy='13' r='3'/%3E
|
||||||
|
%3C/g%3E
|
||||||
|
%3C/svg%3E`;
|
||||||
|
|
||||||
|
return pattern.replace(/[\n\r]+|[\s]{2,}/g, ' ').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private convertOklchStringToHex(oklchString: string): string {
|
||||||
|
const parts = oklchString.split(' ');
|
||||||
|
const l = parseFloat(parts[0]);
|
||||||
|
const c = parseFloat(parts[1]);
|
||||||
|
const h = parseFloat(parts[2]);
|
||||||
|
|
||||||
|
return this.convertOklchToHex(l / 100, c, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
private convertOklchToHex(l: number, c: number, h: number): string {
|
||||||
|
const rgb = chroma.oklch(l, c, h).rgb();
|
||||||
|
|
||||||
|
return chroma(rgb).hex();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
export * from './auth.service';
|
export * from './auth.service';
|
||||||
export * from './local-storage.service';
|
export * from './local-storage.service';
|
||||||
export * from './session-storage.service';
|
export * from './session-storage.service';
|
||||||
|
export * from './theme.service';
|
||||||
|
export * from './background-pattern.service';
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
import { LocalStorageService } from './local-storage.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class ThemeService {
|
||||||
|
private currentTheme: string = '';
|
||||||
|
|
||||||
|
public constructor(private storageService: LocalStorageService) {
|
||||||
|
this.loadInitialTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTheme(): string {
|
||||||
|
return this.currentTheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setTheme(theme: string): void {
|
||||||
|
this.currentTheme = theme;
|
||||||
|
this.storageService.setItem('theme', theme);
|
||||||
|
this.applyTheme(theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
public toggleTheme(): void {
|
||||||
|
this.currentTheme = this.currentTheme === 'light' ? 'dark' : 'light';
|
||||||
|
this.setTheme(this.currentTheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
private applyTheme(theme: string): void {
|
||||||
|
document.body.setAttribute('data-theme', theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadInitialTheme(): void {
|
||||||
|
const savedTheme = this.storageService.getItem<string>('theme');
|
||||||
|
|
||||||
|
this.currentTheme = savedTheme ? savedTheme : 'light';
|
||||||
|
this.applyTheme(this.currentTheme);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,10 @@
|
||||||
// Import PrimeNG styles
|
// Import PrimeNG styles
|
||||||
@import 'primeng/resources/themes/lara-light-blue/theme.css';
|
// @import 'primeng/resources/themes/lara-light-blue/theme.css';
|
||||||
@import 'primeng/resources/primeng.css';
|
// @import 'primeng/resources/primeng.css';
|
||||||
|
|
||||||
// PrimeNG icons
|
// // PrimeNG icons
|
||||||
@import 'primeicons/primeicons.css';
|
// @import 'primeicons/primeicons.css';
|
||||||
|
|
||||||
html,
|
@tailwind base;
|
||||||
body {
|
@tailwind components;
|
||||||
height: 100%;
|
@tailwind utilities;
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
content: [
|
||||||
|
"./src/**/*.{html,ts}",
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [require('daisyui'), require('tailwindcss-animated')],
|
||||||
|
daisyui: {
|
||||||
|
themes: ["light", "dark"],
|
||||||
|
darkMode: ['class', '[data-theme="dark"]']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue