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/router": "^17.3.0",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"chroma-js": "^2.4.2",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dompurify": "^3.1.3",
|
||||
"primeicons": "^7.0.0",
|
||||
|
@ -46,11 +47,14 @@
|
|||
"@angular-eslint/template-parser": "17.2.1",
|
||||
"@angular/cli": "^17.3.0",
|
||||
"@angular/compiler-cli": "^17.3.0",
|
||||
"@types/chroma-js": "^2.4.4",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@typescript-eslint/eslint-plugin": "6.19.0",
|
||||
"@typescript-eslint/parser": "6.19.0",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"concurrently": "^8.2.2",
|
||||
"daisyui": "^4.12.2",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
|
@ -59,7 +63,10 @@
|
|||
"eslint-plugin-unused-imports": "^3.2.0",
|
||||
"jest": "^29.7.0",
|
||||
"jest-preset-angular": "^14.0.3",
|
||||
"postcss": "^8.4.38",
|
||||
"prettier": "3.2.5",
|
||||
"tailwindcss": "^3.4.4",
|
||||
"tailwindcss-animated": "^1.1.2",
|
||||
"typescript": "~5.4.2",
|
||||
"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 { RouterOutlet } from '@angular/router';
|
||||
|
||||
import { ThemeService } from './shared/service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
standalone: true,
|
||||
|
@ -10,5 +12,5 @@ import { RouterOutlet } from '@angular/router';
|
|||
styleUrl: './app.component.scss',
|
||||
})
|
||||
export class AppComponent {
|
||||
public constructor() {}
|
||||
public constructor(private readonly themeService: ThemeService) {}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ const publicRoutes: Routes = [
|
|||
),
|
||||
},
|
||||
{
|
||||
path: 'signup',
|
||||
path: 'welcome',
|
||||
loadComponent: () =>
|
||||
import('./pages/register-root/register-root.component').then(
|
||||
(m) => m.RegisterRootComponent
|
||||
|
|
|
@ -1,20 +1,39 @@
|
|||
<div id="background">
|
||||
<div class="wrapper">
|
||||
<div class="content">
|
||||
@if (verifyStatus() === true) {
|
||||
@if (showRedirectMessage()) {
|
||||
<h1>Es geht gleich los!</h1>
|
||||
<h2>
|
||||
Danke für das bestätigen der E-Mail - Wir leiten dich zum Login
|
||||
weiter!
|
||||
</h2>
|
||||
<div class="bg-primary w-screen h-screen">
|
||||
<div class="modal modal-open">
|
||||
<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 (showRedirectMessage()) {
|
||||
<div class="text-center">
|
||||
<h1 class="font-bold text-3xl pt-5">Your email is verified!</h1>
|
||||
<p class="pt-3 pb-6">
|
||||
Your email {{ email() }} has been successfully verified. will
|
||||
<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) {
|
||||
<div class="text-center">
|
||||
<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 {
|
||||
<div class="text-center">
|
||||
<span class="loading loading-dots loading-lg"></span>
|
||||
</div>
|
||||
}
|
||||
} @else if (verifyStatus() === false) {
|
||||
<h1>Oops, da ist etwas schief gelaufen!</h1>
|
||||
<h2>Der Link ist nicht mehr gültig</h2>
|
||||
} @else {
|
||||
<h1>Verifizierung wird durchgeführt...</h1>
|
||||
}
|
||||
</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 {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
ElementRef,
|
||||
InputSignal,
|
||||
OnInit,
|
||||
WritableSignal,
|
||||
|
@ -12,11 +14,12 @@ import { Router } from '@angular/router';
|
|||
import { delay, filter, tap } from 'rxjs';
|
||||
|
||||
import { VerifyApiService } from '../../api';
|
||||
import { BackgroundPatternService, ThemeService } from '../../shared/service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-email-verify-root',
|
||||
standalone: true,
|
||||
imports: [],
|
||||
imports: [CommonModule],
|
||||
providers: [],
|
||||
templateUrl: './email-verify-root.component.html',
|
||||
styleUrl: './email-verify-root.component.scss',
|
||||
|
@ -24,6 +27,8 @@ import { VerifyApiService } from '../../api';
|
|||
})
|
||||
export class EmailVerifyRootComponent implements OnInit {
|
||||
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>(
|
||||
null
|
||||
);
|
||||
|
@ -31,19 +36,71 @@ export class EmailVerifyRootComponent implements OnInit {
|
|||
|
||||
public constructor(
|
||||
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 {
|
||||
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 {
|
||||
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
|
||||
.verifyControllerVerifyEmail(verifyToken)
|
||||
.pipe(
|
||||
delay(1500),
|
||||
tap((isVerified: boolean) => {
|
||||
this.verifyStatus.set(isVerified);
|
||||
}),
|
||||
|
@ -51,12 +108,10 @@ export class EmailVerifyRootComponent implements OnInit {
|
|||
tap(() => {
|
||||
this.showRedirectMessage.set(true);
|
||||
}),
|
||||
delay(5000)
|
||||
delay(10000)
|
||||
)
|
||||
.subscribe(() => {
|
||||
this.router.navigate(['/signup'], {
|
||||
queryParams: { verified: true, email: email },
|
||||
});
|
||||
this.navigateToWelcomeScreen();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ export class HomeComponent implements OnInit {
|
|||
},
|
||||
(error: HttpErrorResponse) => {
|
||||
if (error.status === 401) {
|
||||
this.router.navigate(['signup'], {
|
||||
this.router.navigate(['welcome'], {
|
||||
queryParams: { login: true },
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,110 +1,301 @@
|
|||
<div id="background">
|
||||
<div class="img-zone">
|
||||
<div class="img-wrapper">
|
||||
@if (userSignupSuccess()) {
|
||||
<div class="success">
|
||||
<h1>Danke für deine Registrierung!</h1>
|
||||
<h2>
|
||||
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>
|
||||
} @else {
|
||||
<div class="headline">
|
||||
<h1>Hi, Welcome to Ticket App.</h1>
|
||||
</div>
|
||||
}
|
||||
@if (!userSignupSuccess()) {
|
||||
<div class="flex h-screen w-screen">
|
||||
<div
|
||||
[ngStyle]="leftBackgroundStyle"
|
||||
class="hidden md:flex md:flex-col md:w-1/2 bg-primary">
|
||||
<div class="flex-1 flex items-start pt-16 px-12">
|
||||
<h1 class="text-3xl text-base-100">[LOGO] APP-NAME</h1>
|
||||
</div>
|
||||
<div class="flex-1 flex flex-col justify-end pb-16 px-12">
|
||||
<blockquote>
|
||||
<p class="text-xl text-base-100 font-semibold">
|
||||
“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>
|
||||
|
||||
@if (!userSignupSuccess()) {
|
||||
<div class="content-zone">
|
||||
<h1>
|
||||
@if (isSignupSignal()) {
|
||||
Anmelden
|
||||
} @else if (isRegisterSignal()) {
|
||||
Registrieren
|
||||
} @else {
|
||||
Erste Schritte
|
||||
}
|
||||
</h1>
|
||||
<!-- Rechter Bereich, immer sichtbar -->
|
||||
<div [ngStyle]="rightBackgroundStyle" class="flex flex-col w-full md:w-1/2">
|
||||
<div class="flex px-12 gap-3">
|
||||
<div class="flex items-start justify-end pt-16">
|
||||
<label class="swap swap-rotate">
|
||||
<input
|
||||
type="checkbox"
|
||||
(change)="toggleTheme()"
|
||||
[checked]="isDarkMode" />
|
||||
|
||||
@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>
|
||||
<!-- 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 class="flex-1 items-start flex justify-end pt-16">
|
||||
@if (isSignupSignal()) {
|
||||
<button
|
||||
(click)="toggleAction('signin')"
|
||||
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 (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
|
||||
pInputText
|
||||
id="email"
|
||||
formControlName="email"
|
||||
aria-describedby="e-mail" />
|
||||
@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="password">
|
||||
<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 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>
|
||||
@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 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 class="signup">
|
||||
<button
|
||||
pButton
|
||||
type="submit"
|
||||
[label]="
|
||||
isSignupSignal()
|
||||
? 'Anmelden'
|
||||
: '✨ Jetzt KOSTENFREI loslegen ✨'
|
||||
"></button>
|
||||
</div>
|
||||
<div class="change-mask">
|
||||
<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)="switchMask()"
|
||||
(keyup.enter)="switchMask()"
|
||||
tabindex="0">
|
||||
@if (isSignupSignal()) {
|
||||
Kein Account? Erstellen Sie jetzt KOSTENFREI einen!
|
||||
} @else {
|
||||
Schon einen Account? Hier einloggen
|
||||
}
|
||||
(click)="toggleAction('signup')"
|
||||
(keypress)="toggleAction('signup')"
|
||||
tabindex="0"
|
||||
class="text-primary cursor-pointer text-xs">
|
||||
Create An Account
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
</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>
|
||||
|
|
|
@ -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,
|
||||
InputSignal,
|
||||
input,
|
||||
ElementRef,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
FormBuilder,
|
||||
|
@ -24,6 +25,7 @@ import { ButtonModule } from 'primeng/button';
|
|||
import { CheckboxModule } from 'primeng/checkbox';
|
||||
import { InputTextModule } from 'primeng/inputtext';
|
||||
import { PasswordModule } from 'primeng/password';
|
||||
import { delay, finalize, tap } from 'rxjs';
|
||||
|
||||
import {
|
||||
Configuration,
|
||||
|
@ -32,13 +34,18 @@ import {
|
|||
UserCredentialsDtoApiModel,
|
||||
} from '../../api';
|
||||
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 {
|
||||
customEmailValidator,
|
||||
customPasswordValidator,
|
||||
} from '../../shared/validator';
|
||||
|
||||
type AuthAction = 'register' | 'signup';
|
||||
type AuthAction = 'signin' | 'signup';
|
||||
|
||||
@Component({
|
||||
selector: 'app-register-root',
|
||||
|
@ -65,47 +72,68 @@ type AuthAction = 'register' | 'signup';
|
|||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
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 login: InputSignal<boolean> = input<boolean>(false);
|
||||
public email: InputSignal<string> = input<string>('');
|
||||
public form: FormGroup | undefined;
|
||||
public isRegisterSignal: WritableSignal<boolean> = signal(false);
|
||||
public isSignupSignal: WritableSignal<boolean> = signal(false);
|
||||
public isDisplayButtons: WritableSignal<boolean> = signal(true);
|
||||
public emailInvalid: WritableSignal<string | null> = signal(null);
|
||||
public passwordInvalid: WritableSignal<string | null> = signal(null);
|
||||
public termsInvalid: WritableSignal<string | null> = signal(null);
|
||||
public form!: FormGroup;
|
||||
public rememberMe: FormControl = new FormControl(false);
|
||||
public isSigninSignal: WritableSignal<boolean> = signal(false);
|
||||
public isSignupSignal: WritableSignal<boolean> = signal(true);
|
||||
public isSignUpSuccess: 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);
|
||||
|
||||
public get isDarkMode(): boolean {
|
||||
return this.themeService.getTheme() === 'dark';
|
||||
}
|
||||
|
||||
public constructor(
|
||||
private readonly formBuilder: FormBuilder,
|
||||
private readonly authService: AuthService,
|
||||
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(() => {
|
||||
if (this.form) {
|
||||
if (this.isRegisterSignal()) {
|
||||
this.form.addControl(
|
||||
'terms',
|
||||
new FormControl(false, Validators.requiredTrue)
|
||||
);
|
||||
} else {
|
||||
this.form.removeControl('terms');
|
||||
}
|
||||
}
|
||||
|
||||
if (this.removeQueryParams()) {
|
||||
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 {
|
||||
this.setBackground();
|
||||
this.initializeForm();
|
||||
this.setupValueChanges();
|
||||
this.preselectForm();
|
||||
|
||||
if ((this.email() && this.verified()) || this.login()) {
|
||||
this.handleRedirect();
|
||||
|
@ -113,52 +141,116 @@ export class RegisterRootComponent implements OnInit {
|
|||
}
|
||||
}
|
||||
|
||||
public preselectForm(): void {
|
||||
if (!this.email() || !this.verified()) {
|
||||
const email = this.sessionStorageService.getItem('email');
|
||||
public setBackground(): void {
|
||||
const theme = this.themeService.getTheme();
|
||||
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 {
|
||||
if (action === 'register') {
|
||||
this.isRegisterSignal.set(true);
|
||||
this.resetFormValidation();
|
||||
|
||||
if (action === 'signin') {
|
||||
this.handlePreselect();
|
||||
this.isSigninSignal.set(true);
|
||||
this.isSignupSignal.set(false);
|
||||
} else {
|
||||
this.isRegisterSignal.set(false);
|
||||
this.isSigninSignal.set(false);
|
||||
this.isSignupSignal.set(true);
|
||||
}
|
||||
this.isDisplayButtons.set(false);
|
||||
}
|
||||
|
||||
public onSubmit(): void {
|
||||
this.markControlsAsTouchedAndDirty(['email', 'password', 'terms']);
|
||||
this.markControlsAsTouchedAndDirty(['email', 'password']);
|
||||
|
||||
if (this.form?.valid) {
|
||||
if (this.isRegisterSignal()) {
|
||||
this.signup(this.form.value);
|
||||
} else {
|
||||
if (this.isSigninSignal()) {
|
||||
this.signin(this.form.value);
|
||||
} else {
|
||||
this.signup(this.form.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public switchMask(): void {
|
||||
this.resetFormValidation();
|
||||
private handlePreselect(): void {
|
||||
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.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 {
|
||||
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({
|
||||
email: new FormControl('', {
|
||||
email: new FormControl(emailValue, {
|
||||
validators: [Validators.required, customEmailValidator()],
|
||||
updateOn: 'change',
|
||||
}),
|
||||
|
@ -166,18 +258,15 @@ export class RegisterRootComponent implements OnInit {
|
|||
validators: [Validators.required, customPasswordValidator()],
|
||||
updateOn: 'change',
|
||||
}),
|
||||
terms: new FormControl(false, {
|
||||
validators: [Validators.requiredTrue],
|
||||
updateOn: 'change',
|
||||
}),
|
||||
});
|
||||
|
||||
this.rememberMe.setValue(rememberMeValue);
|
||||
}
|
||||
|
||||
private handleRedirect(): void {
|
||||
if (this.verified()) {
|
||||
this.isDisplayButtons.set(false);
|
||||
this.isRegisterSignal.set(false);
|
||||
this.isSignupSignal.set(true);
|
||||
this.isSigninSignal.set(true);
|
||||
this.isSignupSignal.set(false);
|
||||
}
|
||||
if (this.email()) {
|
||||
this.form?.get('email')?.setValue(decodeURIComponent(atob(this.email())));
|
||||
|
@ -185,8 +274,7 @@ export class RegisterRootComponent implements OnInit {
|
|||
|
||||
if (this.login()) {
|
||||
this.isSignupSignal.set(true);
|
||||
this.isDisplayButtons.set(false);
|
||||
this.isRegisterSignal.set(false);
|
||||
this.isSigninSignal.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -250,7 +338,7 @@ export class RegisterRootComponent implements OnInit {
|
|||
}
|
||||
|
||||
private resetFormValidation(): void {
|
||||
['email', 'password', 'terms'].forEach((controlName: string) => {
|
||||
['email', 'password'].forEach((controlName: string) => {
|
||||
this.resetControlValidation(controlName);
|
||||
});
|
||||
}
|
||||
|
@ -267,8 +355,20 @@ export class RegisterRootComponent implements OnInit {
|
|||
}
|
||||
|
||||
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
|
||||
.signin(logiCredentials)
|
||||
.pipe(
|
||||
tap(() => this.isLoading.set(true)),
|
||||
delay(1000),
|
||||
finalize(() => this.isLoading.set(false))
|
||||
)
|
||||
.subscribe((response: SigninResponseDtoApiModel) => {
|
||||
if (response) {
|
||||
this.router.navigate(['/dashboard']);
|
||||
|
@ -277,10 +377,17 @@ export class RegisterRootComponent implements OnInit {
|
|||
}
|
||||
|
||||
private signup(logiCredentials: UserCredentialsDtoApiModel): void {
|
||||
this.isLoading.set(true);
|
||||
this.authService
|
||||
.signup(logiCredentials)
|
||||
.pipe(
|
||||
delay(1000),
|
||||
tap(() => this.isLoading.set(true)),
|
||||
finalize(() => this.isLoading.set(false))
|
||||
)
|
||||
.subscribe((response: SuccessDtoApiModel) => {
|
||||
if (response.success) {
|
||||
this.openModal();
|
||||
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 './local-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/resources/themes/lara-light-blue/theme.css';
|
||||
@import 'primeng/resources/primeng.css';
|
||||
// @import 'primeng/resources/themes/lara-light-blue/theme.css';
|
||||
// @import 'primeng/resources/primeng.css';
|
||||
|
||||
// PrimeNG icons
|
||||
@import 'primeicons/primeicons.css';
|
||||
// // PrimeNG icons
|
||||
// @import 'primeicons/primeicons.css';
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
|
|
@ -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