From a1646cc537af942f2e617e03d6dbbef85cb580e4 Mon Sep 17 00:00:00 2001 From: Diego Sousa Date: Mon, 22 Sep 2025 17:03:58 -0300 Subject: [PATCH 1/6] feat: create utility for password generation --- README.md | 1 + components/seo/PasswordGeneratorSEO.tsx | 99 +++++++++++++ components/utils/password-generator.utils.ts | 77 ++++++++++ components/utils/tools-list.ts | 6 + pages/utilities/password-generator.tsx | 147 +++++++++++++++++++ 5 files changed, 330 insertions(+) create mode 100644 components/seo/PasswordGeneratorSEO.tsx create mode 100644 components/utils/password-generator.utils.ts create mode 100644 pages/utilities/password-generator.tsx diff --git a/README.md b/README.md index b76671c..5dbf094 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ Here is the list of all utilities: - [WebP Converter](https://jam.dev/utilities/webp-converter) - [SQL Minifer](https://jam.dev/utilities/sql-minifier) - [Internet Speed Test](https://jam.dev/utilities/internet-speed-test) +- [Password Generator](https://jam.dev/utilities/password-generator) ### Built With diff --git a/components/seo/PasswordGeneratorSEO.tsx b/components/seo/PasswordGeneratorSEO.tsx new file mode 100644 index 0000000..24bdb44 --- /dev/null +++ b/components/seo/PasswordGeneratorSEO.tsx @@ -0,0 +1,99 @@ +export default function PasswordGeneratorSEO() { + return ( +
+
+

+ Generate secure passwords instantly with this free online password + generator. Whether you're protecting your accounts, apps, or + sensitive data, Jam's Password Generator creates strong, + randomized passwords to keep you safe. +

+
+ +
+

+ Just select your preferences(lowercase letters, uppercase letters, + numbers, and special characters) and click "Generate". Built + with 💜 by the developers at Jam, completely open-source and + ad-free. +

+
+ +
+

How to Use Jam's Password Generator

+

+ Quickly create a strong password tailored to your needs. Follow these + simple steps: +

+
    +
  • + Select options:
    Choose to include lowercase, uppercase, + numbers, and/or symbols. +
  • +
  • + Set length:
    Pick a password length. +
  • +
  • + Generate:
    Click "Generate" to instantly get a + secure password. +
  • +
  • + Copy:
    Copy your password safely with one click. +
  • +
+
+ +
+

Why Use a Password Generator?

+

+ Manually created passwords are often weak and easy to guess. A password + generator helps you avoid common mistakes and ensures your credentials + are strong. +

+
    +
  • + Security:
    Random, unpredictable passwords are harder to + crack. +
  • +
  • + Customization:
    Adjust the length and types of + characters to meet site requirements. +
  • +
  • + Convenience:
    Generate reliable passwords instantly, + without thinking. +
  • +
+
+ +
+

FAQs

+
    +
  • + Is this password generator free?
    Yes. It's free, + open-source, and ad-free. +
  • +
  • + Are the generated passwords stored?
    No. Passwords are + generated in your browser and never sent to our servers. +
  • +
  • + How long should my password be?
    For strong security, + use at least 12 characters. For maximum protection, 20+ characters + are recommended. +
  • +
  • + Can I use these passwords everywhere?
    Yes. They are + compatible with any website, app, or system that accepts custom + passwords. +
  • +
  • + How strong are the passwords?
    They are generated using + cryptographically secure random values, making them highly resistant + to attacks. +
  • +
+
+
+ ); +} diff --git a/components/utils/password-generator.utils.ts b/components/utils/password-generator.utils.ts new file mode 100644 index 0000000..8057405 --- /dev/null +++ b/components/utils/password-generator.utils.ts @@ -0,0 +1,77 @@ + +export const getRandomInt = (max: number) => { + const arr = new Uint32Array(1); + window.crypto.getRandomValues(arr); + return arr[0] % max; +}; + +export class PasswordBuilder { + private LOWER = "abcdefghijklmnopqrstuvwxyz"; + private UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + private NUMS = "0123456789"; + private SYMBOLS = "!@#$%^&*()_+[]{}|;:,.<>?/~`-="; + private characterPool = '' + + constructor( + private includeLowercase: boolean, + private includeUppercase: boolean, + private includeNumbers: boolean, + private includeSymbols: boolean, + private desiredLength: number + ) { + let pool = ""; + if (this.includeLowercase) pool += this.LOWER; + if (this.includeUppercase) pool += this.UPPER; + if (this.includeNumbers) pool += this.NUMS; + if (this.includeSymbols) pool += this.SYMBOLS; + this.characterPool = pool; + } + + GetMandatoryChars() { + const picks: string[] = []; + + if (this.includeLowercase) { + const lowers = this.LOWER.split(""); + picks.push(lowers[getRandomInt(lowers.length)]); + } + if (this.includeUppercase) { + const uppers = this.UPPER.split(""); + picks.push(uppers[getRandomInt(uppers.length)]); + } + if (this.includeNumbers) { + const numbers = this.NUMS.split(""); + picks.push(numbers[getRandomInt(numbers.length)]); + } + if (this.includeSymbols) { + const symbols = this.SYMBOLS.split(""); + picks.push(symbols[getRandomInt(symbols.length)]); + } + + return picks; + } + + Build() { + const categoriesAmount = [this.includeLowercase, this.includeUppercase, this.includeNumbers, this.includeSymbols].filter((b) => !!b); + + const finalLen = Math.max(1, Math.min(128, this.desiredLength)); + const useLen = finalLen < categoriesAmount.length ? categoriesAmount.length : finalLen; + + // start with selected categories mandatory characters + const guaranteed = this.GetMandatoryChars(); + const resultChars: string[] = [...guaranteed]; + + // fill the rest of the input length + for (let i = resultChars.length; i < useLen; i++) { + const idx = getRandomInt(this.characterPool.length); + resultChars.push(this.characterPool[idx]); + } + + // Fisher-Yates shuffle algorithm + for (let i = resultChars.length - 1; i > 0; i--) { + const j = getRandomInt(i + 1); + [resultChars[i], resultChars[j]] = [resultChars[j], resultChars[i]]; + } + + return resultChars.join(""); + } +} \ No newline at end of file diff --git a/components/utils/tools-list.ts b/components/utils/tools-list.ts index fe66855..f26ad9e 100644 --- a/components/utils/tools-list.ts +++ b/components/utils/tools-list.ts @@ -155,4 +155,10 @@ export const tools = [ "Test your internet connection speed with accurate measurements by using Cloudflare's global network.", link: "/utilities/internet-speed-test", }, + { + title: "Password Generator", + description: + "Generate strong and secure passwords instantly with Jam's free online Password Generator. Choose your preferences or use all options by default (uppercase letters, lowercase letters, numbers, and special characters) and get a reliable password in one click!", + link: "/utilities/password-generator", + }, ]; diff --git a/pages/utilities/password-generator.tsx b/pages/utilities/password-generator.tsx new file mode 100644 index 0000000..f67c4d6 --- /dev/null +++ b/pages/utilities/password-generator.tsx @@ -0,0 +1,147 @@ +import { useCallback, useState, useRef } from "react"; +import { Textarea } from "@/components/ds/TextareaComponent"; +import PageHeader from "@/components/PageHeader"; +import { Card } from "@/components/ds/CardComponent"; +import { Button } from "@/components/ds/ButtonComponent"; +import { Label } from "@/components/ds/LabelComponent"; +import Header from "@/components/Header"; +import { Checkbox } from "@/components/ds/CheckboxComponent"; +import { CMDK } from "@/components/CMDK"; +import { useCopyToClipboard } from "@/components/hooks/useCopyToClipboard"; +import CallToActionGrid from "@/components/CallToActionGrid"; +import Meta from "@/components/Meta"; +import { Input } from "@/components/ds/InputComponent"; +import { PasswordBuilder } from "@/components/utils/password-generator.utils"; +import PasswordGeneratorSEO from "@/components/seo/PasswordGeneratorSEO"; +import GitHubContribution from "@/components/GitHubContribution"; + +export default function PasswordGenerator() { + const [password, setPassword] = useState(""); + const [length, setLength] = useState(16); + const [includeLowercase, setIncludeLowercase] = useState(true); + const [includeUppercase, setIncludeUppercase] = useState(true); + const [includeNumbers, setIncludeNumbers] = useState(true); + const [includeSymbols, setIncludeSymbols] = useState(true); + const { buttonText, handleCopy } = useCopyToClipboard(); + const outputRef = useRef(null); + + const generatePassword = useCallback(() => { + const builder = new PasswordBuilder( + includeLowercase, + includeUppercase, + includeNumbers, + includeSymbols, + length + ) + setPassword(builder.Build()); + }, [includeLowercase, includeUppercase, includeNumbers, includeSymbols, length]); + + const strengthInfo = useCallback(() => { + const types = [includeLowercase, includeUppercase, includeNumbers, includeSymbols].filter(Boolean).length; + + if (length >= 20 && types >= 3) { + return { label: "Very Strong", className: "text-green-600 font-semibold" }; + } + if (length >= 12 && types >= 3) { + return { label: "Strong", className: "text-green-500" }; + } + if (length >= 10 && types >= 2) { + return { label: "Medium", className: "text-yellow-500" }; + } + return { label: "Weak", className: "text-red-500" }; + }, [length, includeLowercase, includeUppercase, includeNumbers, includeSymbols]); + + return ( +
+ +
+ + +
+ +
+ +
+ +
+
+ +
Select the desired options
+
+ +
+ + + + + + + +
+ +
+ + setLength(Number(e.target.value))} + className='w-24' + /> +
Strength: {strengthInfo().label}
+
+ +
+
+ +
+ +
+
+ +