mirror of
https://github.com/danielvici/tool-website.git
synced 2026-01-16 23:01:26 +00:00
add: password generator; change: Startpage Header made bold
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { Routes } from '@angular/router';
|
import { Routes } from '@angular/router';
|
||||||
import { Startpage } from './sites/startpage/startpage';
|
import { Startpage } from './sites/startpage/startpage';
|
||||||
import {TestIt} from './sites/test-it/test-it';
|
import {TestIt} from './sites/test-it/test-it';
|
||||||
|
import {GeneratorPassword} from './sites/generator/password/generator-password.component';
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{
|
{
|
||||||
@@ -11,6 +12,10 @@ export const routes: Routes = [
|
|||||||
path: '',
|
path: '',
|
||||||
component: Startpage,
|
component: Startpage,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'generator/password',
|
||||||
|
component: GeneratorPassword,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '**',
|
path: '**',
|
||||||
redirectTo: '',
|
redirectTo: '',
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<div>
|
||||||
|
<div class="prose prose-invert max-w-none mb-6 text-gray-300 space-y-1">
|
||||||
|
<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>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { CustomDescription } from './custom-description';
|
||||||
|
|
||||||
|
describe('CustomDescription', () => {
|
||||||
|
let component: CustomDescription;
|
||||||
|
let fixture: ComponentFixture<CustomDescription>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [CustomDescription]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(CustomDescription);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
13
src/app/components/custom-description/custom-description.ts
Normal file
13
src/app/components/custom-description/custom-description.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import {Component, input} from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-custom-description',
|
||||||
|
imports: [],
|
||||||
|
templateUrl: './custom-description.html',
|
||||||
|
styleUrl: './custom-description.css',
|
||||||
|
})
|
||||||
|
export class CustomDescription {
|
||||||
|
descriptionText = input.required<string>();
|
||||||
|
additionalListEnabled = input<boolean>(false);
|
||||||
|
additionalListText =input<string[]>();
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<div>
|
||||||
|
<div class="mb-8">
|
||||||
|
<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>
|
||||||
|
<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" -->
|
||||||
|
</div>
|
||||||
|
<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}}*
|
||||||
|
}
|
||||||
|
</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>
|
||||||
|
</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>
|
||||||
|
</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>
|
||||||
|
</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>
|
||||||
|
</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>
|
||||||
|
<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>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { GeneratorPassword } from './generator-password.component';
|
||||||
|
|
||||||
|
describe('Password', () => {
|
||||||
|
let component: GeneratorPassword;
|
||||||
|
let fixture: ComponentFixture<GeneratorPassword>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [GeneratorPassword]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(GeneratorPassword);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-password',
|
||||||
|
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) {
|
||||||
|
|
||||||
|
this.passwordGenerator = this.fb.group({
|
||||||
|
length: [16],
|
||||||
|
includeLetters: [true],
|
||||||
|
includeNumbers: [true],
|
||||||
|
includeSymbols: [true]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
generatePassword() {
|
||||||
|
this.generatedPassword = '';
|
||||||
|
const length = this.passwordGenerator.value.length;
|
||||||
|
let includeLetters = this.passwordGenerator.value.includeLetters;
|
||||||
|
let includeNumbers = this.passwordGenerator.value.includeNumbers;
|
||||||
|
let includeSymbols = this.passwordGenerator.value.includeSymbols;
|
||||||
|
|
||||||
|
if(!includeLetters && !includeNumbers && !includeSymbols) {
|
||||||
|
this.passwordGenerator.value.includeLetters = true;
|
||||||
|
includeLetters = true;
|
||||||
|
console.log(this.passwordGenerator.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const uppercaseChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||||
|
const lowercaseChars = 'abcdefghijklmnopqrstuvwxyz';
|
||||||
|
const numberChars = '0123456789';
|
||||||
|
const symbolChars = '!@#$%^&*()_+[]{}|;:,.<>?';
|
||||||
|
|
||||||
|
let allChars = '';
|
||||||
|
if (includeLetters) allChars += uppercaseChars + lowercaseChars;
|
||||||
|
if (includeNumbers) allChars += numberChars;
|
||||||
|
if (includeSymbols) allChars += symbolChars;
|
||||||
|
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
this.generatedPassword += allChars[Math.floor(Math.random() * allChars.length)]
|
||||||
|
}
|
||||||
|
|
||||||
|
this.generated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
copyPassword() {
|
||||||
|
navigator.clipboard.writeText(this.generatedPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
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' }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
[textSizeInPx]="24"
|
[textSizeInPx]="24"
|
||||||
text="Tools"
|
text="Tools"
|
||||||
textColor="white"
|
textColor="white"
|
||||||
class="font-bold"
|
fontWeight="bold"
|
||||||
></app-custom-title>
|
></app-custom-title>
|
||||||
</div>
|
</div>
|
||||||
<div class="gap-6 ml-4 max-w-7xl mx-auto space-y-12">
|
<div class="gap-6 ml-4 max-w-7xl mx-auto space-y-12">
|
||||||
@@ -21,8 +21,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="ml-4 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
<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) {
|
@for (tool of feature.tools; track $index) {
|
||||||
<app-custom-card [title]="tool.name" [description]="tool.description" descriptionColor="oklch(70.4% 0.04 256.788)"
|
<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>
|
||||||
[destination]="tool.link" [enabled]="tool.enabled"></app-custom-card>
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user