feature/init-tailwind-daisyui #13

Merged
igorpropisnov merged 10 commits from feature/init-tailwind-daisyui into main 2024-06-26 19:22:33 +02:00
7 changed files with 362 additions and 100 deletions
Showing only changes of commit 0f1efdab5d - Show all commits

View File

@ -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,6 +47,7 @@
"@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",
@ -64,6 +66,7 @@
"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"
},

View File

@ -32,6 +32,9 @@ dependencies:
'@types/dompurify':
specifier: ^3.0.5
version: 3.0.5
chroma-js:
specifier: ^2.4.2
version: 2.4.2
crypto-js:
specifier: ^4.2.0
version: 4.2.0
@ -79,6 +82,9 @@ devDependencies:
'@angular/compiler-cli':
specifier: ^17.3.0
version: 17.3.0(@angular/compiler@17.3.0)(typescript@5.4.2)
'@types/chroma-js':
specifier: ^2.4.4
version: 2.4.4
'@types/crypto-js':
specifier: ^4.2.2
version: 4.2.2
@ -133,6 +139,9 @@ devDependencies:
tailwindcss:
specifier: ^3.4.4
version: 3.4.4
tailwindcss-animated:
specifier: ^1.1.2
version: 1.1.2(tailwindcss@3.4.4)
typescript:
specifier: ~5.4.2
version: 5.4.2
@ -3155,6 +3164,10 @@ packages:
'@types/node': 20.11.27
dev: true
/@types/chroma-js@2.4.4:
resolution: {integrity: sha512-/DTccpHTaKomqussrn+ciEvfW4k6NAHzNzs/sts1TCqg333qNxOhy8TNIoQCmbGG3Tl8KdEhkGAssb1n3mTXiQ==}
dev: true
/@types/connect-history-api-fallback@1.5.4:
resolution: {integrity: sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==}
dependencies:
@ -4352,6 +4365,10 @@ packages:
engines: {node: '>=10'}
dev: true
/chroma-js@2.4.2:
resolution: {integrity: sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A==}
dev: false
/chrome-trace-event@1.0.3:
resolution: {integrity: sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==}
engines: {node: '>=6.0'}
@ -9543,6 +9560,14 @@ packages:
tslib: 2.6.2
dev: true
/tailwindcss-animated@1.1.2(tailwindcss@3.4.4):
resolution: {integrity: sha512-SI4owS5ojserhgEYIZA/uFVdNjU2GMB2P3sjtjmFA52VxoUi+Hht6oR5+RdT+CxrX9cNNYEa+vbTWHvN9zbj3w==}
peerDependencies:
tailwindcss: '>=3.1.0'
dependencies:
tailwindcss: 3.4.4
dev: true
/tailwindcss@3.4.4:
resolution: {integrity: sha512-ZoyXOdJjISB7/BcLTR6SEsLgKtDStYyYZVLsUtWChO4Ps20CBad7lfJKVDiejocV4ME1hLmyY0WJE3hSDcmQ2A==}
engines: {node: '>=14.0.0'}

View File

@ -108,7 +108,9 @@
</div>
}
</div> -->
<div class="flex h-screen w-screen">
@if (!userSignupSuccess()) {
<div class="flex h-screen w-screen">
<div 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>
@ -156,28 +158,84 @@
</label>
</div>
<div class="flex-1 items-start flex justify-end pt-16">
<button class="btn btn-primary btn-outline no-animation">Login</button>
@if (isSignupSignal()) {
<button
(click)="toggleAction('signin')"
class="btn btn-primary btn-outline no-animation">
Login
</button>
}
@if (isSigninSignal()) {
<button
(click)="toggleAction('signup')"
class="btn btn-primary btn-outline no-animation animate-shake animate-thrice animate-duration-[250ms] animate-delay-[5000ms] animate-ease-in-out animate-normal animate-fill-forwards">
New User? Register Now!
</button>
}
</div>
</div>
<!-- Zentrierter Inhalt für Account-Erstellung -->
<div class="flex-1 flex flex-col justify-center items-center px-12">
@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 class="flex gap-4 flex-col items-center py-6 w-full max-w-md">
<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 class="input-group input-group-sm">
<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"
placeholder="name@example.com"
class="input input-bordered input w-full" />
class="grow"
placeholder="name@example.com" />
</label>
</div>
<div class="form-control w-full">
<label class="input-group input-group-sm">
<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"
placeholder="password"
class="input input-bordered input w-full" />
class="grow"
value="" />
</label>
</div>
<button class="btn w-full btn-primary font-semibold">
@ -192,5 +250,115 @@
</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">
<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"
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">
Sign In
</button>
</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">
<div class="hidden md:flex md:flex-col md:w-full bg-primary"></div>
</div>
}
<div class="modal modal-open" *ngIf="isDialogOpen()">
<div
[ngStyle]="backgroundStyle"
class="modal-box w-11/12 h-3/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="2"
stroke="currentColor"
class="size-36">
<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-6xl pt-5">Check your E-Mail</h1>
<div class="flex flex-col items-center">
<p class="pt-8 text-xl font-semibold">
We sent an email link to complete your registration.
</p>
<p class="text-xs pt-20 pb-2">
<a class="cursor-pointer">
Didn´t recieve the email? Click here to send it again.
</a>
</p>
</div>
</div>
</div>
</div>

View File

@ -9,6 +9,7 @@ import {
effect,
InputSignal,
input,
ElementRef,
} from '@angular/core';
import {
FormBuilder,
@ -34,6 +35,7 @@ import {
import { ApiConfiguration } from '../../config/api-configuration';
import {
AuthService,
BackgroundPatternService,
SessionStorageService,
ThemeService,
} from '../../shared/service';
@ -42,7 +44,7 @@ import {
customPasswordValidator,
} from '../../shared/validator';
type AuthAction = 'register' | 'signup';
type AuthAction = 'signin' | 'signup';
@Component({
selector: 'app-register-root',
@ -69,17 +71,19 @@ type AuthAction = 'register' | 'signup';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class RegisterRootComponent implements OnInit {
public backgroundStyle: { '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 form!: FormGroup;
public isSigninSignal: WritableSignal<boolean> = signal(false);
public isSignupSignal: WritableSignal<boolean> = signal(true);
//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 userSignupSuccess: WritableSignal<boolean> = signal(false);
public isDialogOpen: WritableSignal<boolean> = signal(false);
private removeQueryParams: WritableSignal<boolean> = signal(false);
public get isDarkMode(): boolean {
@ -91,11 +95,13 @@ export class RegisterRootComponent implements OnInit {
private readonly authService: AuthService,
private readonly router: Router,
private readonly sessionStorageService: SessionStorageService,
private readonly themeService: ThemeService
private readonly themeService: ThemeService,
private readonly el: ElementRef,
private readonly backgroundPatternService: BackgroundPatternService
) {
effect(() => {
if (this.form) {
if (this.isRegisterSignal()) {
if (this.isSigninSignal()) {
this.form.addControl(
'terms',
new FormControl(false, Validators.requiredTrue)
@ -112,6 +118,7 @@ export class RegisterRootComponent implements OnInit {
}
public ngOnInit(): void {
this.setBackground();
this.initializeForm();
this.setupValueChanges();
this.preselectForm();
@ -122,6 +129,24 @@ export class RegisterRootComponent implements OnInit {
}
}
public setBackground(): void {
const color = getComputedStyle(this.el.nativeElement).getPropertyValue(
'--p'
);
const svgUrl = this.backgroundPatternService.getWigglePattern(color, 0.04);
this.backgroundStyle = { 'background-image': `url("${svgUrl}")` };
}
public openModal(): void {
this.isDialogOpen.set(true);
}
public closeModal(): void {
this.isDialogOpen.set(false);
}
public toggleTheme(): void {
this.themeService.toggleTheme();
}
@ -135,24 +160,24 @@ export class RegisterRootComponent implements OnInit {
}
public toggleAction(action: AuthAction): void {
if (action === 'register') {
this.isRegisterSignal.set(true);
if (action === 'signin') {
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);
//this.isDisplayButtons.set(false);
}
public onSubmit(): void {
this.markControlsAsTouchedAndDirty(['email', 'password', 'terms']);
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);
}
}
}
@ -162,10 +187,10 @@ export class RegisterRootComponent implements OnInit {
if (this.isSignupSignal()) {
this.isSignupSignal.set(false);
this.isRegisterSignal.set(true);
} else if (this.isRegisterSignal()) {
this.isSigninSignal.set(true);
} else if (this.isSigninSignal()) {
this.isSignupSignal.set(true);
this.isRegisterSignal.set(false);
this.isSigninSignal.set(false);
}
}
@ -188,8 +213,8 @@ export class RegisterRootComponent implements OnInit {
private handleRedirect(): void {
if (this.verified()) {
this.isDisplayButtons.set(false);
this.isRegisterSignal.set(false);
//this.isDisplayButtons.set(false);
this.isSigninSignal.set(false);
this.isSignupSignal.set(true);
}
if (this.email()) {
@ -198,8 +223,8 @@ export class RegisterRootComponent implements OnInit {
if (this.login()) {
this.isSignupSignal.set(true);
this.isDisplayButtons.set(false);
this.isRegisterSignal.set(false);
//this.isDisplayButtons.set(false);
this.isSigninSignal.set(false);
}
}
@ -294,6 +319,7 @@ export class RegisterRootComponent implements OnInit {
.signup(logiCredentials)
.subscribe((response: SuccessDtoApiModel) => {
if (response.success) {
this.openModal();
this.userSignupSuccess.set(true);
}
});

View File

@ -0,0 +1,39 @@
import { Injectable } from '@angular/core';
import chroma from 'chroma-js';
@Injectable({
providedIn: 'root',
})
export class BackgroundPatternService {
public getWigglePattern(color: string, opacity: number = 0.08): 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();
}
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();
}
}

View File

@ -2,3 +2,4 @@ export * from './auth.service';
export * from './local-storage.service';
export * from './session-storage.service';
export * from './theme.service';
export * from './background-pattern.service';

View File

@ -6,7 +6,7 @@ module.exports = {
theme: {
extend: {},
},
plugins: [require('daisyui')],
plugins: [require('daisyui'), require('tailwindcss-animated')],
daisyui: {
themes: ["light", "dark"],
darkMode: ['class', '[data-theme="dark"]']