files formatted; add: qrcode generator

This commit is contained in:
danielvici123
2025-11-19 19:14:40 +01:00
parent 79ff5eb6d1
commit 061376402b
25 changed files with 627 additions and 141 deletions

View File

@@ -1,6 +1,6 @@
<app-navbar class="fixed top-0 left-0 w-screen h-16 z-[999]"></app-navbar>
<div class="p-8 pt-24 h-screen text-white bg-black-eerie mx-auto flex justify-center-safe" >
<!-- pt-16 bg-black-eerie h-screen flex justify-center-safe mx-auto " -->
<div class="p-8 pt-24 min-h-screen text-white bg-zinc-900 mx-auto flex justify-center-safe">
<!-- pt-16 bg-black-eerie h-screen flex justify-center-safe mx-auto " -->
<router-outlet></router-outlet>
</div>

View File

@@ -1,23 +1,28 @@
import { Routes } from '@angular/router';
import { Startpage } from './sites/startpage/startpage';
import {TestIt} from './sites/test-it/test-it';
import {GeneratorPassword} from './sites/generator/password/generator-password.component';
import { TestIt } from './sites/test-it/test-it';
import { GeneratorPassword } from './sites/generator/password/generator-password.component';
import { GeneratorQrcode } from './sites/generator/qrcode/generator-qrcode';
export const routes: Routes = [
{
path:'test',
path: 'test',
component: TestIt,
},
{
path: '',
component: Startpage,
},
}, // GENERATORS
{
path: 'generator/password',
component: GeneratorPassword,
},
{
path: 'generator/qrcode',
component: GeneratorQrcode,
},
{
path: '**',
redirectTo: '',
}
},
];

View File

@@ -1,8 +1,8 @@
@if (enabled()) {
<div
class="h-full
hover:cursor-pointer flex flex-col p-6 rounded-lg border border-gray-700 bg-zinc-800 transition-colors duration-200 hover:border-blue-500"
[routerLink]="destination()">
class="h-full hover:cursor-pointer flex flex-col p-6 rounded-lg border border-gray-700 bg-zinc-800 transition-colors duration-200 hover:border-blue-500"
[routerLink]="destination()"
>
<app-custom-title
[text]="title()"
[textSizeInPx]="titleSizeInPx()"
@@ -10,6 +10,6 @@
fontWeight="bold"
class="mb-2"
></app-custom-title>
<span [ngStyle]="{'color': descriptionColor()}">{{ description() }}</span>
<span [ngStyle]="{ color: descriptionColor() }">{{ description() }}</span>
</div>
}

View File

@@ -1,7 +1,7 @@
import { Component, input } from '@angular/core';
import { CustomTitle } from '../custom-title/custom-title';
import {RouterLink} from '@angular/router';
import {NgStyle} from '@angular/common';
import { RouterLink } from '@angular/router';
import { NgStyle } from '@angular/common';
@Component({
selector: 'app-custom-card',
@@ -10,7 +10,6 @@ import {NgStyle} from '@angular/common';
styleUrl: './custom-card.css',
})
export class CustomCard {
title = input.required<string>();
titleSizeInPx = input<number>(20);
titleColor = input<string>();

View File

@@ -1,10 +1,10 @@
<div>
<div class="prose prose-invert max-w-none mb-6 text-gray-300 space-y-1">
<p>{{descriptionText()}}</p>
<p>{{ descriptionText() }}</p>
@if (additionalListEnabled()) {
<ul class="list-disc pl-6 text-gray-300 space-y-1">
@for (item of additionalListText(); track $index) {
<li>{{item}}</li>
<li>{{ item }}</li>
}
</ul>
}

View File

@@ -8,9 +8,8 @@ describe('CustomDescription', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [CustomDescription]
})
.compileComponents();
imports: [CustomDescription],
}).compileComponents();
fixture = TestBed.createComponent(CustomDescription);
component = fixture.componentInstance;

View File

@@ -1,4 +1,4 @@
import {Component, input} from '@angular/core';
import { Component, input } from '@angular/core';
@Component({
selector: 'app-custom-description',
@@ -9,5 +9,5 @@ import {Component, input} from '@angular/core';
export class CustomDescription {
descriptionText = input.required<string>();
additionalListEnabled = input<boolean>(false);
additionalListText =input<string[]>();
additionalListText = input<string[]>();
}

View File

@@ -1,6 +1,8 @@
<a
[ngStyle]="{
'font-size': textSizeInPx() + 'px',
color: textColor(),
'font-weight': fontWeight()}"
>{{ text() }}</a>
'font-size': textSizeInPx() + 'px',
color: textColor(),
'font-weight': fontWeight(),
}"
>{{ text() }}</a
>

View File

@@ -1,5 +1,5 @@
import { Component, input } from '@angular/core';
import {NgClass, NgStyle} from '@angular/common';
import { NgClass, NgStyle } from '@angular/common';
@Component({
selector: 'app-custom-title',
@@ -11,5 +11,5 @@ export class CustomTitle {
text = input.required<string>();
textSizeInPx = input<number>(16);
textColor = input<string>();
fontWeight = input<string>("normal");
fontWeight = input<string>('normal');
}

View File

@@ -1,62 +1,105 @@
<div>
<div class="mb-8">
<app-custom-title text="Password Generator" [textSizeInPx]="36" fontWeight="bold"></app-custom-title>
<app-custom-title
text="Password Generator"
[textSizeInPx]="36"
fontWeight="bold"
></app-custom-title>
</div>
<div class="bg-zinc-800 rounded-lg shadow-md p-6">
<app-custom-description
descriptionText="Generate secure passwords with customizable options. For maximum security, it's recommend to:"
[additionalListEnabled]="true" [additionalListText]="[
'Use at least 16 characters for sensitive accounts',
'Include a mix of letters, numbers, and special characters',
'Use different passwords for each account']"></app-custom-description>
[additionalListEnabled]="true"
[additionalListText]="[
'Use at least 16 characters for sensitive accounts',
'Include a mix of letters, numbers, and special characters',
'Use different passwords for each account',
]"
></app-custom-description>
<div>
<div [formGroup]="passwordGenerator" class="relative mb-6">
<div class="flex flex-col">
<label class="mb-2">Password Length: <span class="ml-2 font-bold text-xl">{{ passwordGenerator.value.length }}</span></label>
<input type="range" min="4" max="64" formControlName="length" class="w-full border bg-white transition-[color,box-shadow]"> <!--class="w-full mt-2 mb-4 bg-blue-500 h-2 appearance-none" -->
<label class="mb-2"
>Password Length:
<span class="ml-2 font-bold text-xl">{{ passwordGenerator.value.length }}</span></label
>
<input
type="range"
min="4"
max="64"
formControlName="length"
class="w-full border bg-white transition-[color,box-shadow]"
/>
<!--class="w-full mt-2 mb-4 bg-blue-500 h-2 appearance-none" -->
</div>
<div> <!-- strength description -->
<p class="text-sm mt-2" [class]="getStrengthDescription(passwordGenerator.value.length).color">
<div>
<!-- strength description -->
<p
class="text-sm mt-2"
[class]="getStrengthDescription(passwordGenerator.value.length).color"
>
@if (getStrengthDescription(passwordGenerator.value.length)) {
{{getStrengthDescription(passwordGenerator.value.length).text}}*
{{ getStrengthDescription(passwordGenerator.value.length).text }}*
}
</p>
</div>
<div class="flex flex-wrap gap-4 my-4">
<!-- CHECKBOXES -->
<div>
<label class="flex items-center"><input type="checkbox" formControlName="includeLetters"
class="rounded border-gray-600 bg-zinc-700 text-blue-500 mr-2 focus:ring-blue-500 focus:ring-offset-zinc-800">Letters</label>
<label class="flex items-center"
><input
type="checkbox"
formControlName="includeLetters"
class="rounded border-gray-600 bg-zinc-700 text-blue-500 mr-2 focus:ring-blue-500 focus:ring-offset-zinc-800"
/>Letters</label
>
</div>
<div>
<label class="flex items-center"><input type="checkbox" formControlName="includeNumbers"
class="rounded border-gray-600 bg-zinc-700 text-blue-500 mr-2 focus:ring-blue-500 focus:ring-offset-zinc-800">Numbers</label>
<label class="flex items-center"
><input
type="checkbox"
formControlName="includeNumbers"
class="rounded border-gray-600 bg-zinc-700 text-blue-500 mr-2 focus:ring-blue-500 focus:ring-offset-zinc-800"
/>Numbers</label
>
</div>
<div>
<label class="flex items-center"><input type="checkbox" formControlName="includeSymbols"
class="rounded border-gray-600 bg-zinc-700 text-blue-500 mr-2 focus:ring-blue-500 focus:ring-offset-zinc-800">Special Characters</label>
<label class="flex items-center"
><input
type="checkbox"
formControlName="includeSymbols"
class="rounded border-gray-600 bg-zinc-700 text-blue-500 mr-2 focus:ring-blue-500 focus:ring-offset-zinc-800"
/>Special Characters</label
>
</div>
</div>
<p class="text-sm my-2">*Based on the length of the password</p>
<div>
<!-- SUBMIT -->
<button (click)="generatePassword()"
class="active:top-1 cursor-pointer w-full bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 transition-colors">Generate
Password</button>
<button
(click)="generatePassword()"
class="active:top-1 cursor-pointer w-full bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 transition-colors"
>
Generate Password
</button>
</div>
@if (generated) {
<div class="mt-4">
<label class="block text-sm font-medium text-gray-300 mb-2">Generated Password:</label>
<div class="flex">
<div class="flex-1 bg-zinc-700 text-white rounded-l-md border border-gray-600 p-2">{{generatedPassword}}
<div class="mt-4">
<label class="block text-sm font-medium text-gray-300 mb-2">Generated Password:</label>
<div class="flex">
<div class="flex-1 bg-zinc-700 text-white rounded-l-md border border-gray-600 p-2">
{{ generatedPassword }}
</div>
<button
class="cursor-pointer bg-zinc-700 px-4 rounded-r-md border-t border-r border-b border-gray-600 text-gray-300 hover:bg-zinc-600 transition-colors"
(click)="copyPassword()"
>
Copy
</button>
</div>
<button
class="cursor-pointer bg-zinc-700 px-4 rounded-r-md border-t border-r border-b border-gray-600 text-gray-300 hover:bg-zinc-600 transition-colors"
(click)="copyPassword()">Copy</button>
</div>
</div>
}
</div>
</div>
</div>
</div>
</div>

View File

@@ -8,9 +8,8 @@ describe('Password', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [GeneratorPassword]
})
.compileComponents();
imports: [GeneratorPassword],
}).compileComponents();
fixture = TestBed.createComponent(GeneratorPassword);
component = fixture.componentInstance;

View File

@@ -1,34 +1,26 @@
import { Component } from '@angular/core';
import {CustomTitle} from '../../../components/custom-title/custom-title';
import {FormBuilder, FormsModule, ReactiveFormsModule} from '@angular/forms';
import {CustomDescription} from '../../../components/custom-description/custom-description';
import { CustomTitle } from '../../../components/custom-title/custom-title';
import { FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { CustomDescription } from '../../../components/custom-description/custom-description';
@Component({
selector: 'app-password',
imports: [
CustomTitle,
CustomDescription,
ReactiveFormsModule,
],
imports: [CustomTitle, CustomDescription, ReactiveFormsModule],
templateUrl: './generator-password.component.html',
styleUrl: './generator-password.component.css',
})
export class GeneratorPassword {
passwordGenerator!: any;
generatedPassword: string = '';
generated: boolean = false;
constructor(private fb:FormBuilder) {
constructor(private fb: FormBuilder) {
this.passwordGenerator = this.fb.group({
length: [16],
includeLetters: [true],
includeNumbers: [true],
includeSymbols: [true]
})
includeSymbols: [true],
});
}
generatePassword() {
@@ -38,10 +30,10 @@ export class GeneratorPassword {
let includeNumbers = this.passwordGenerator.value.includeNumbers;
let includeSymbols = this.passwordGenerator.value.includeSymbols;
if(!includeLetters && !includeNumbers && !includeSymbols) {
if (!includeLetters && !includeNumbers && !includeSymbols) {
this.passwordGenerator.value.includeLetters = true;
includeLetters = true;
console.log(this.passwordGenerator.value)
console.log(this.passwordGenerator.value);
}
const uppercaseChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
@@ -55,7 +47,7 @@ export class GeneratorPassword {
if (includeSymbols) allChars += symbolChars;
for (let i = 0; i < length; i++) {
this.generatedPassword += allChars[Math.floor(Math.random() * allChars.length)]
this.generatedPassword += allChars[Math.floor(Math.random() * allChars.length)];
}
this.generated = true;
@@ -66,9 +58,9 @@ export class GeneratorPassword {
}
public getStrengthDescription = (len: number) => {
if (len < 10) return { text: 'Very Weak', color: 'text-red-500' }
if (len < 15) return { text: 'Weak', color: 'text-orange-500' }
if (len < 20) return { text: 'Strong', color: 'text-green-500' }
return { text: 'Very Strong', color: 'text-emerald-500' }
}
if (len < 10) return { text: 'Very Weak', color: 'text-red-500' };
if (len < 15) return { text: 'Weak', color: 'text-orange-500' };
if (len < 20) return { text: 'Strong', color: 'text-green-500' };
return { text: 'Very Strong', color: 'text-emerald-500' };
};
}

View File

@@ -0,0 +1,64 @@
<div class="p-8 text-white">
<div class="mx-auto">
<div class="mb-8">
<app-custom-title
text="QR Code Generator"
[textSizeInPx]="36"
fontWeight="bold"
></app-custom-title>
</div>
<div class="bg-zinc-800 rounded-lg shadow-md p-6 max-w-2xl mx-auto">
<div>
<app-custom-description
descriptionText="Generate QR codes for text, URLs, or any other content. Customize the size and colors, then download the QR code as a PNG image."
></app-custom-description>
</div>
<div [formGroup]="qrCodeDetailsForm" class="space-y-6">
<div>
<label class="block text-sm font-medium text-gray-300 mb-2">Text or URL</label>
<input type="text" placeholder="Enter text or URL" formControlName="text"
class="w-full bg-zinc-700 text-white rounded-md border border-gray-600 p-2" />
</div>
<div>
<label class="block text-sm font-medium text-gray-300 mb-2">
Size: {{ qrCodeDetailsForm.value.size }}x{{ qrCodeDetailsForm.value.size }} pixels
</label>
<input type="range" min="100" max="800" step="100" formControlName="size" class="w-full" />
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-300 mb-2">QR Code Color</label>
<input type="color" formControlName="darkColor"
class="w-full h-10 rounded-md cursor-pointer bg-zinc-700 p-1" />
</div>
<div>
<label class="block text-sm font-medium text-gray-300 mb-2">Background Color</label>
<input type="color" formControlName="lightColor"
class="w-full h-10 rounded-md cursor-pointer bg-zinc-700 p-1" />
</div>
</div>
<button type="button" (click)="generateQR()"
class="w-full bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 transition-colors">
Generate QR Code
</button>
</div>
<div class="flex justify-center mt-6 flex-col items-center space-y-4">
<canvas #canvas class="hidden" #ziel></canvas>
@if (qrDataUrl) {
<img [src]="qrDataUrl" alt="Generated QR Code" class="mx-auto border-4 border-white rounded-lg max-w-[500px] max-h-[500px]" />
<button (click)="downloadQR()"
class="w-full bg-green-500 text-white py-2 px-4 rounded-md hover:bg-green-600 transition-colors">
Download QR Code
</button>
}
</div>
</div>
</div>
</div>

View File

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

View File

@@ -0,0 +1,61 @@
import { Component, ViewChild, ElementRef } from '@angular/core';
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
import QRCode from 'qrcode';
import { CustomTitle } from "../../../components/custom-title/custom-title";
import { CustomDescription } from "../../../components/custom-description/custom-description";
@Component({
selector: 'app-generator-qrcode',
templateUrl: './generator-qrcode.html',
styleUrls: ['./generator-qrcode.css'],
imports: [ReactiveFormsModule, CustomTitle, CustomDescription],
})
export class GeneratorQrcode {
qrDataUrl: string | null = null;
qrCodeDetailsForm: any;
@ViewChild('canvas', { static: false }) canvasRef!: ElementRef<HTMLCanvasElement>;
@ViewChild('ziel') zielRef!: ElementRef;
constructor(private fb: FormBuilder) {
this.qrCodeDetailsForm = this.fb.group({
text: ['', Validators.required],
size: [300, Validators.required],
darkColor: ['#000000', Validators.required],
lightColor: ['#FFFFFF', Validators.required],
});
}
async generateQR(): Promise<void> {
if (!this.qrCodeDetailsForm.valid) return;
const canvas = this.canvasRef?.nativeElement;
if (!canvas) return;
try {
await QRCode.toCanvas(canvas, this.qrCodeDetailsForm.value.text, {
width: this.qrCodeDetailsForm.value.size,
margin: 2,
color: {
dark: this.qrCodeDetailsForm.value.darkColor,
light: this.qrCodeDetailsForm.value.lightColor,
},
});
this.qrDataUrl = canvas.toDataURL('image/png');
this.zielRef.nativeElement.scrollIntoView({ behavior: 'smooth' });
} catch (err) {
console.error('Error generating QR code:', err);
}
}
downloadQR(): void {
if (!this.qrDataUrl) return;
const link = document.createElement('a');
link.download = 'qrcode.png';
link.href = this.qrDataUrl;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}

View File

@@ -21,7 +21,13 @@
</div>
<div class="ml-4 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
@for (tool of feature.tools; track $index) {
<app-custom-card [title]="tool.name" [description]="tool.description" descriptionColor="oklch(70.4% 0.04 256.788)" [destination]="tool.link" [enabled]="tool.enabled"></app-custom-card>
<app-custom-card
[title]="tool.name"
[description]="tool.description"
descriptionColor="oklch(70.4% 0.04 256.788)"
[destination]="tool.link"
[enabled]="tool.enabled"
></app-custom-card>
}
</div>
</div>

View File

@@ -2,7 +2,7 @@ import { Component } from '@angular/core';
import { NgOptimizedImage } from '@angular/common';
import { CustomTitle } from '../../components/custom-title/custom-title';
import { CustomCard } from '../../components/custom-card/custom-card';
import {ReactiveFormsModule} from '@angular/forms';
import { ReactiveFormsModule } from '@angular/forms';
@Component({
selector: 'app-startpage',
@@ -11,33 +11,42 @@ import {ReactiveFormsModule} from '@angular/forms';
imports: [CustomTitle, CustomCard, ReactiveFormsModule],
})
export class Startpage {
features = [{
name: 'Generators',
enabled: true,
tools: [{
name: 'Password Generator',
description: 'Create secure passwords with custom options',
link: '/generator/password',
features = [
{
name: 'Generators',
enabled: true,
}, {
name: 'QR Code Generator',
description: 'Generate and download QR codes for any text or URL',
link: '/generator/qr',
tools: [
{
name: 'Password Generator',
description: 'Create secure passwords with custom options',
link: '/generator/password',
enabled: true,
},
{
name: 'QR Code Generator',
description: 'Generate and download QR codes for any text or URL',
link: '/generator/qrcode',
enabled: true,
},
],
},
{
name: 'Tests',
enabled: true,
}]}, {
name: 'Tests',
enabled: true,
tools: [{
name: 'Test 1',
description: 'This is a just test description.',
link: '/test',
enabled: true,
},{
name: 'Test 2',
description: 'This is a just test description.',
link: '/test',
enabled: false,
}]
}];
tools: [
{
name: 'Test 1',
description: 'This is a just test description.',
link: '/test',
enabled: true,
},
{
name: 'Test 2',
description: 'This is a just test description.',
link: '/test',
enabled: false,
},
],
},
];
}

View File

@@ -4,6 +4,8 @@
<button
class="text-xl w-fit bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 transition-colors hover:cursor-pointer"
routerLink="/"
>Home</button>
>
Home
</button>
</div>
</div>

View File

@@ -8,9 +8,8 @@ describe('TestIt', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [TestIt]
})
.compileComponents();
imports: [TestIt],
}).compileComponents();
fixture = TestBed.createComponent(TestIt);
component = fixture.componentInstance;

View File

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

View File

@@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<title>ToolsWebsite</title>
<title>Tools Website</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="app/assets/logo.ico" />