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)
+}