diff --git a/src/app/_components/navbar.tsx b/src/app/_components/navbar.tsx index 4fcfa56..495a106 100644 --- a/src/app/_components/navbar.tsx +++ b/src/app/_components/navbar.tsx @@ -34,7 +34,7 @@ type Props = { } export function Navbar({ isOnDrawer = false }: Props) { return ( -
+
diff --git a/src/app/misc/password/_components/password-display.tsx b/src/app/misc/password/_components/password-display.tsx new file mode 100644 index 0000000..e1b1ad4 --- /dev/null +++ b/src/app/misc/password/_components/password-display.tsx @@ -0,0 +1,38 @@ +import { + isLowerCasedLetter, + isNumber, + isSpecial, + isUpperCasedLetter +} from '../_lib/char-filters' + +type Props = { + password: string +} +export function PasswordDisplay({ password }: Props) { + return ( +
+ {password.split('').map((char, i) => { + if (isUpperCasedLetter(char) || isLowerCasedLetter(char)) + return ( + + {char} + + ) + if (isNumber(char)) + return ( + + {char} + + ) + if (isSpecial(char)) + return ( + + {char} + + ) + + return {char} + })} +
+ ) +} diff --git a/src/app/misc/password/_lib/char-filters.ts b/src/app/misc/password/_lib/char-filters.ts new file mode 100644 index 0000000..7895f51 --- /dev/null +++ b/src/app/misc/password/_lib/char-filters.ts @@ -0,0 +1,4 @@ +export const isUpperCasedLetter = (char: string) => /[A-Z]/.test(char) +export const isLowerCasedLetter = (char: string) => /[a-z]/.test(char) +export const isNumber = (char: string) => /[0-9]/.test(char) +export const isSpecial = (char: string) => /[!@#$%&*]/.test(char) diff --git a/src/app/misc/password/_lib/generate-password.ts b/src/app/misc/password/_lib/generate-password.ts new file mode 100644 index 0000000..e0a8d45 --- /dev/null +++ b/src/app/misc/password/_lib/generate-password.ts @@ -0,0 +1,30 @@ +import { getRandomPassword } from './get-random-password' +import { getAlphabet } from './get-alphabet' + +export type Options = { + size: number + characters: { + upper: boolean + lower: boolean + numbers: boolean + special: boolean + } +} + +export function generatePassword({ + size, + characters: { lower, numbers, special, upper } +}: Options) { + const isAllDisabled = !(lower || upper || special || numbers) + + const characters = isAllDisabled + ? { lower: true, numbers, special, upper } + : { lower, numbers, special, upper } + + const alphabet = getAlphabet(characters) + + return getRandomPassword({ + size, + alphabet + }) +} diff --git a/src/app/misc/password/_lib/get-alphabet.ts b/src/app/misc/password/_lib/get-alphabet.ts new file mode 100644 index 0000000..25847b6 --- /dev/null +++ b/src/app/misc/password/_lib/get-alphabet.ts @@ -0,0 +1,33 @@ +import { shuffleArray } from '~/shared/lib/shuffle-array' + +import { + isLowerCasedLetter, + isNumber, + isSpecial, + isUpperCasedLetter +} from './char-filters' + +export function getAlphabet({ + lower, + numbers, + special, + upper +}: { + lower: boolean + numbers: boolean + special: boolean + upper: boolean +}) { + const alphabet = + 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%&*'.split( + '' + ) + + return shuffleArray(alphabet).filter( + char => + (upper && isUpperCasedLetter(char)) || + (lower && isLowerCasedLetter(char)) || + (numbers && isNumber(char)) || + (special && isSpecial(char)) + ) +} diff --git a/src/app/misc/password/_lib/get-random-password.ts b/src/app/misc/password/_lib/get-random-password.ts new file mode 100644 index 0000000..57ace50 --- /dev/null +++ b/src/app/misc/password/_lib/get-random-password.ts @@ -0,0 +1,15 @@ +type Props = { + size: number + alphabet: string[] +} + +export function getRandomPassword({ alphabet, size }: Props): string { + let newPasswordCharsArray: string[] = [] + + for (let index = 0; index < size; index++) { + const charIndex = Math.floor(Math.random() * alphabet.length) + newPasswordCharsArray.push(alphabet[charIndex]) + } + + return newPasswordCharsArray.join('') +} diff --git a/src/app/misc/password/layout.tsx b/src/app/misc/password/layout.tsx new file mode 100644 index 0000000..924934f --- /dev/null +++ b/src/app/misc/password/layout.tsx @@ -0,0 +1,10 @@ +import { ReactNode } from 'react' +import type { Metadata } from 'next' + +export const metadata: Metadata = { + title: 'Password' +} + +export default function PasswordLayout({ children }: { children: ReactNode }) { + return children +} diff --git a/src/app/misc/password/page.tsx b/src/app/misc/password/page.tsx new file mode 100644 index 0000000..052a629 --- /dev/null +++ b/src/app/misc/password/page.tsx @@ -0,0 +1,143 @@ +'use client' + +import { ChangeEvent, useEffect, useState } from 'react' + +import { Button } from '~/shared/components/button' +import { Input } from '~/shared/components/input' + +import { Options, generatePassword } from './_lib/generate-password' +import { PasswordDisplay } from './_components/password-display' +import { Slider } from '~/shared/components/slider' +import { Label } from '~/shared/components/label' +import { Minus, Plus, RefreshCw } from 'lucide-react' +import { ToggleGroup, ToggleGroupItem } from '~/shared/components/toggle-group' +import { CopyButton } from '~/shared/components/copy-button' + +export default function Page() { + const [passwordLength, setPasswordLength] = useState(20) + const [characters, setCharacters] = useState({ + lower: true, + upper: true, + numbers: true, + special: true + }) + + const defaultOptions: Options = { + size: passwordLength, + characters + } + + const [password, setPassword] = useState( + generatePassword(defaultOptions) + ) + + const handleGeneratePassword = (options?: Options) => + setPassword(generatePassword(options ?? defaultOptions)) + + function handleSetPasswordLength(value: number[]) { + if (value[0] < 8 || value[0] > 128) return + + setPasswordLength(value[0]) + handleGeneratePassword({ ...defaultOptions, size: value[0] }) + } + + function handleEnabledChars(enabledList: string[]) { + const characters = { + lower: enabledList.includes('lower'), + upper: enabledList.includes('upper'), + numbers: enabledList.includes('numbers'), + special: enabledList.includes('special') + } + + const isAllDisabled = !( + characters.lower || + characters.upper || + characters.numbers || + characters.special + ) + + const enabledCharacters = !isAllDisabled + ? characters + : { + lower: true, + upper: false, + numbers: false, + special: false + } + + setCharacters(enabledCharacters) + handleGeneratePassword({ ...defaultOptions, characters: enabledCharacters }) + } + + const enabledCharactersList = [ + ...(characters.lower ? ['lower'] : []), + ...(characters.upper ? ['upper'] : []), + ...(characters.numbers ? ['numbers'] : []), + ...(characters.special ? ['special'] : []) + ] + + return ( +
+ +
+
+ +
+ + + +
+
+
+ Enabled characters + + ABC + abc + 123 + !@$ + +
+
+
+ + +
+
+ ) +} diff --git a/src/shared/lib/shuffle-array.ts b/src/shared/lib/shuffle-array.ts new file mode 100644 index 0000000..8f228fd --- /dev/null +++ b/src/shared/lib/shuffle-array.ts @@ -0,0 +1,6 @@ +export function shuffleArray(array: T[]) { + return array + .map(value => ({ value, sort: Math.random() })) + .sort((a, b) => a.sort - b.sort) + .map(({ value }) => value) +}