From 9721459b4fca332d481381eaa81ed1cda661cf61 Mon Sep 17 00:00:00 2001 From: javigaralva Date: Thu, 10 Nov 2022 18:24:32 +0100 Subject: [PATCH 1/6] feat: add change character on click --- ...zed-letter.tsx => colorized-character.tsx} | 23 ++++++++-------- .../components/colorized-password.tsx | 24 ++++++++++++++--- .../javigaralva/password-generator-app.tsx | 26 ++++++++++++++++--- 3 files changed, 55 insertions(+), 18 deletions(-) rename src/components/javigaralva/components/{colorized-letter.tsx => colorized-character.tsx} (71%) diff --git a/src/components/javigaralva/components/colorized-letter.tsx b/src/components/javigaralva/components/colorized-character.tsx similarity index 71% rename from src/components/javigaralva/components/colorized-letter.tsx rename to src/components/javigaralva/components/colorized-character.tsx index 5348b69fa..3dfe76353 100644 --- a/src/components/javigaralva/components/colorized-letter.tsx +++ b/src/components/javigaralva/components/colorized-character.tsx @@ -8,34 +8,35 @@ import { isUpperCaseLetter } from '../utils/is-upper-case-letter' import { isSymbol } from '../utils/is-symbol' import { isNumber } from '../utils/is-number' -type ColorizedLetterStyles = { +type ColorizedCharacterStyles = { classNames: string style: CSSProperties } -interface ColorizedLetterProps { - letter: string +interface ColorizedCharacterProps { + character: string + onClick: () => void } -export function ColorizedLetter({ letter }: ColorizedLetterProps) { - const { classNames, style } = getStyles(letter) +export function ColorizedCharacter({ character, onClick }: ColorizedCharacterProps) { + const { classNames, style } = getStyles(character) return ( - - {letter} + + {character} ) } -function getStyles(letter: string): ColorizedLetterStyles { +function getStyles(character: string): ColorizedCharacterStyles { let classNames = 'text-white' let style: CSSProperties = {} - if (isNumber(letter)) { + if (isNumber(character)) { classNames = 'text-orange-600' style = getBigDotsStyle() - } else if (isSymbol(letter)) { + } else if (isSymbol(character)) { classNames = 'text-lime-600' style = getBigCircleDottedStyle() style.textShadow = 'rgb(116 255 77 / 60%) -1px -1px 6px, rgb(124 127 255 / 0%) 1px 1px 6px' - } else if (isUpperCaseLetter(letter)) { + } else if (isUpperCaseLetter(character)) { classNames = 'text-white/50' style = getSquareAndVerticalLinesStyle() } else { diff --git a/src/components/javigaralva/components/colorized-password.tsx b/src/components/javigaralva/components/colorized-password.tsx index 80e7396c6..abbcf6b38 100644 --- a/src/components/javigaralva/components/colorized-password.tsx +++ b/src/components/javigaralva/components/colorized-password.tsx @@ -1,13 +1,29 @@ -import { ColorizedLetter } from './colorized-letter' +import { ColorizedCharacter } from './colorized-character' + +type OnClickCharacterHandlerParams = { + character: string + index: number +} +export type OnClickCharacterHandler = ( + onClickCharacterHandlerParams: OnClickCharacterHandlerParams +) => void interface ColorizedPasswordProps { password: string + onClickCharacter: OnClickCharacterHandler } -export function ColorizedPassword({ password }: ColorizedPasswordProps) { +export function ColorizedPassword({ password, onClickCharacter }: ColorizedPasswordProps) { + const handleOnClickCharacter: OnClickCharacterHandler = ({ character, index }) => { + onClickCharacter({ character, index }) + } return (
- {password.split('').map((letter, i) => ( - + {password.split('').map((character, i) => ( + handleOnClickCharacter({ character, index: i })} + /> ))}
) diff --git a/src/components/javigaralva/password-generator-app.tsx b/src/components/javigaralva/password-generator-app.tsx index 9320128d3..6b07f19d1 100644 --- a/src/components/javigaralva/password-generator-app.tsx +++ b/src/components/javigaralva/password-generator-app.tsx @@ -1,6 +1,6 @@ import { useState, useEffect, type ChangeEventHandler } from 'react' import { sample } from './utils/sample' -import { ColorizedPassword } from './components/colorized-password' +import { ColorizedPassword, OnClickCharacterHandler } from './components/colorized-password' import { UPPER_CASE_LETTERS_LIST, LOWER_CASE_LETTERS_LIST, @@ -31,6 +31,22 @@ function generatePassword(options: GeneratePasswordOptions) { '' ) } +interface ReplacePasswordCharacterAtParams { + password: string + index: number + options: GeneratePasswordOptions +} +function replacePasswordCharacterAt({ + password, + index, + options +}: ReplacePasswordCharacterAtParams): string { + const areParamsValid = index >= 0 && index <= password.length - 1 + if (!areParamsValid) return password + + const character = getRandomCharacter(options) + return password.substring(0, index) + character + password.substring(index + character.length) +} function getRandomCharacter(options: GeneratePasswordOptions) { return getRandomCharacterPickerFunction(options)?.() ?? '' @@ -76,6 +92,10 @@ export default function Main() { setPasswordLength(Number(e.target.value)) } + const handleOnClickCharacter: OnClickCharacterHandler = ({ index }) => { + setPassword(replacePasswordCharacterAt({ password, index, options: DEFAULT_PASSWORD_OPTIONS })) + } + useEffect(() => { setPassword( generatePassword({ @@ -86,11 +106,11 @@ export default function Main() { }, [passwordLength]) return ( -
+
- +
From 5d95767a22e02c49e9e37c22cdeac6e22942ab9a Mon Sep 17 00:00:00 2001 From: javigaralva Date: Thu, 10 Nov 2022 18:24:57 +0100 Subject: [PATCH 2/6] refactor: add usePasswordGenerator --- .../components/colorized-password.tsx | 4 +- .../javigaralva/constants/constants.tsx | 12 ++ .../hooks/use-password-generator.tsx | 41 +++++++ .../javigaralva/password-generator-app.tsx | 115 ++---------------- .../generate-password/generate-password.ts | 62 ++++++++++ 5 files changed, 125 insertions(+), 109 deletions(-) create mode 100644 src/components/javigaralva/hooks/use-password-generator.tsx create mode 100644 src/components/javigaralva/utils/generate-password/generate-password.ts diff --git a/src/components/javigaralva/components/colorized-password.tsx b/src/components/javigaralva/components/colorized-password.tsx index abbcf6b38..353262c0f 100644 --- a/src/components/javigaralva/components/colorized-password.tsx +++ b/src/components/javigaralva/components/colorized-password.tsx @@ -10,11 +10,11 @@ export type OnClickCharacterHandler = ( interface ColorizedPasswordProps { password: string - onClickCharacter: OnClickCharacterHandler + onClickCharacter?: OnClickCharacterHandler } export function ColorizedPassword({ password, onClickCharacter }: ColorizedPasswordProps) { const handleOnClickCharacter: OnClickCharacterHandler = ({ character, index }) => { - onClickCharacter({ character, index }) + onClickCharacter?.({ character, index }) } return (
diff --git a/src/components/javigaralva/constants/constants.tsx b/src/components/javigaralva/constants/constants.tsx index 3201eaaba..dab6f6a04 100644 --- a/src/components/javigaralva/constants/constants.tsx +++ b/src/components/javigaralva/constants/constants.tsx @@ -1,3 +1,5 @@ +import { GeneratePasswordOptions } from '../utils/generate-password/generate-password' + const LETTERS = 'abcdefghijklmnopqrstuvwxyz' export const NUMBERS = '0123456789' export const SYMBOLS = '\'\\¡!"·$%&()=€@[]{}^/+-*' @@ -5,3 +7,13 @@ export const UPPER_CASE_LETTERS_LIST = LETTERS.toUpperCase().split('') export const LOWER_CASE_LETTERS_LIST = LETTERS.toLowerCase().split('') export const NUMBERS_LIST = NUMBERS.split('') export const SYMBOLS_LIST = SYMBOLS.split('') +export const MIN_PASSWORD_LENGTH = 1 +export const MAX_PASSWORD_LENGTH = 30 + +export const DEFAULT_PASSWORD_OPTIONS: GeneratePasswordOptions = { + length: 10, + hasUpperCaseLetters: true, + hasLowerCaseLetters: true, + hasNumbers: true, + hasSymbols: true +} diff --git a/src/components/javigaralva/hooks/use-password-generator.tsx b/src/components/javigaralva/hooks/use-password-generator.tsx new file mode 100644 index 000000000..3d12794aa --- /dev/null +++ b/src/components/javigaralva/hooks/use-password-generator.tsx @@ -0,0 +1,41 @@ +import { useState, useEffect } from 'react' +import { DEFAULT_PASSWORD_OPTIONS } from '../constants/constants' +import { + generatePassword, + replacePasswordCharacterAt +} from '../utils/generate-password/generate-password' + +export function usePasswordGenerator() { + const [passwordLength, setPasswordLength] = useState(DEFAULT_PASSWORD_OPTIONS.length) + const [password, setPassword] = useState('') + + const handleGeneratePassword = () => { + setPassword( + generatePassword({ + ...DEFAULT_PASSWORD_OPTIONS, + length: passwordLength + }) + ) + } + + const handleCharacterReplacement = ({ index }: { index: number }) => { + setPassword(replacePasswordCharacterAt({ password, index, options: DEFAULT_PASSWORD_OPTIONS })) + } + + useEffect(() => { + setPassword( + generatePassword({ + ...DEFAULT_PASSWORD_OPTIONS, + length: passwordLength + }) + ) + }, [passwordLength]) + + return { + passwordLength, + setPasswordLength, + password, + generatePassword: handleGeneratePassword, + replaceCharacterAt: handleCharacterReplacement + } +} diff --git a/src/components/javigaralva/password-generator-app.tsx b/src/components/javigaralva/password-generator-app.tsx index 6b07f19d1..e5782b3a2 100644 --- a/src/components/javigaralva/password-generator-app.tsx +++ b/src/components/javigaralva/password-generator-app.tsx @@ -1,123 +1,24 @@ -import { useState, useEffect, type ChangeEventHandler } from 'react' -import { sample } from './utils/sample' -import { ColorizedPassword, OnClickCharacterHandler } from './components/colorized-password' -import { - UPPER_CASE_LETTERS_LIST, - LOWER_CASE_LETTERS_LIST, - NUMBERS_LIST, - SYMBOLS_LIST -} from './constants/constants' - -interface GeneratePasswordOptions { - length: number - hasUpperCaseLetters: boolean - hasLowerCaseLetters: boolean - hasNumbers: boolean - hasSymbols: boolean -} -const MIN_PASSWORD_LENGTH = 1 -const MAX_PASSWORD_LENGTH = 30 -const DEFAULT_PASSWORD_OPTIONS: GeneratePasswordOptions = { - length: 10, - hasUpperCaseLetters: true, - hasLowerCaseLetters: true, - hasNumbers: true, - hasSymbols: true -} - -function generatePassword(options: GeneratePasswordOptions) { - return Array.from({ length: options.length }).reduce( - (acc: string) => acc + getRandomCharacter(options), - '' - ) -} -interface ReplacePasswordCharacterAtParams { - password: string - index: number - options: GeneratePasswordOptions -} -function replacePasswordCharacterAt({ - password, - index, - options -}: ReplacePasswordCharacterAtParams): string { - const areParamsValid = index >= 0 && index <= password.length - 1 - if (!areParamsValid) return password - - const character = getRandomCharacter(options) - return password.substring(0, index) + character + password.substring(index + character.length) -} - -function getRandomCharacter(options: GeneratePasswordOptions) { - return getRandomCharacterPickerFunction(options)?.() ?? '' -} - -function getRandomCharacterPickerFunction(options: GeneratePasswordOptions) { - const choosableStrategy = getChoosableStrategies(options) - return sample(choosableStrategy) -} - -function getChoosableStrategies(options: GeneratePasswordOptions) { - return [ - options.hasUpperCaseLetters ? sampleUpperCaseLetter : undefined, - options.hasLowerCaseLetters ? sampleLowerCaseLetter : undefined, - options.hasNumbers ? sampleNumber : undefined, - options.hasSymbols ? sampleSymbol : undefined - ].filter(Boolean) -} - -const sampleUpperCaseLetter = makeSampleCharacterFunction(UPPER_CASE_LETTERS_LIST) -const sampleLowerCaseLetter = makeSampleCharacterFunction(LOWER_CASE_LETTERS_LIST) -const sampleNumber = makeSampleCharacterFunction(NUMBERS_LIST) -const sampleSymbol = makeSampleCharacterFunction(SYMBOLS_LIST) - -function makeSampleCharacterFunction(characters: string[]) { - return () => sample(characters) -} +import { ColorizedPassword } from './components/colorized-password' +import { MAX_PASSWORD_LENGTH, MIN_PASSWORD_LENGTH } from './constants/constants' +import { usePasswordGenerator } from './hooks/use-password-generator' export default function Main() { - const [passwordLength, setPasswordLength] = useState(DEFAULT_PASSWORD_OPTIONS.length) - const [password, setPassword] = useState('') - - const handleGeneratePassword = () => { - setPassword( - generatePassword({ - ...DEFAULT_PASSWORD_OPTIONS, - length: passwordLength - }) - ) - } - - const handleOnChangeSlider: ChangeEventHandler = (e) => { - setPasswordLength(Number(e.target.value)) - } - - const handleOnClickCharacter: OnClickCharacterHandler = ({ index }) => { - setPassword(replacePasswordCharacterAt({ password, index, options: DEFAULT_PASSWORD_OPTIONS })) - } - - useEffect(() => { - setPassword( - generatePassword({ - ...DEFAULT_PASSWORD_OPTIONS, - length: passwordLength - }) - ) - }, [passwordLength]) + const { passwordLength, setPasswordLength, password, generatePassword, replaceCharacterAt } = + usePasswordGenerator() return (
- +
@@ -127,7 +28,7 @@ export default function Main() { min={MIN_PASSWORD_LENGTH} max={MAX_PASSWORD_LENGTH} value={passwordLength} - onChange={handleOnChangeSlider} + onChange={(e) => setPasswordLength(Number(e.target.value))} />
diff --git a/src/components/javigaralva/utils/generate-password/generate-password.ts b/src/components/javigaralva/utils/generate-password/generate-password.ts new file mode 100644 index 000000000..a09df6ba0 --- /dev/null +++ b/src/components/javigaralva/utils/generate-password/generate-password.ts @@ -0,0 +1,62 @@ +import { sample } from '../sample' +import { + UPPER_CASE_LETTERS_LIST, + LOWER_CASE_LETTERS_LIST, + NUMBERS_LIST, + SYMBOLS_LIST +} from '../../constants/constants' + +export interface GeneratePasswordOptions { + length: number + hasUpperCaseLetters: boolean + hasLowerCaseLetters: boolean + hasNumbers: boolean + hasSymbols: boolean +} +export function generatePassword(options: GeneratePasswordOptions) { + return Array.from({ length: options.length }).reduce( + (acc: string) => acc + getRandomCharacter(options), + '' + ) +} +interface ReplacePasswordCharacterAtParams { + password: string + index: number + options: GeneratePasswordOptions +} +export function replacePasswordCharacterAt({ + password, + index, + options +}: ReplacePasswordCharacterAtParams): string { + const areParamsValid = index >= 0 && index <= password.length - 1 + if (!areParamsValid) { + return password + } + + const character = getRandomCharacter(options) + return password.substring(0, index) + character + password.substring(index + character.length) +} +function getRandomCharacter(options: GeneratePasswordOptions) { + return getRandomCharacterPickerFunction(options)?.() ?? '' +} +function getRandomCharacterPickerFunction(options: GeneratePasswordOptions) { + const choosableStrategy = getChoosableStrategies(options) + return sample(choosableStrategy) +} +function getChoosableStrategies(options: GeneratePasswordOptions) { + return [ + options.hasUpperCaseLetters ? sampleUpperCaseLetter : undefined, + options.hasLowerCaseLetters ? sampleLowerCaseLetter : undefined, + options.hasNumbers ? sampleNumber : undefined, + options.hasSymbols ? sampleSymbol : undefined + ].filter(Boolean) +} +const sampleUpperCaseLetter = makeSampleCharacterFunction(UPPER_CASE_LETTERS_LIST) +const sampleLowerCaseLetter = makeSampleCharacterFunction(LOWER_CASE_LETTERS_LIST) +const sampleNumber = makeSampleCharacterFunction(NUMBERS_LIST) +const sampleSymbol = makeSampleCharacterFunction(SYMBOLS_LIST) + +function makeSampleCharacterFunction(characters: string[]) { + return () => sample(characters) +} From 8487a63c748713b596fc29869563fb62be212051 Mon Sep 17 00:00:00 2001 From: javigaralva Date: Thu, 10 Nov 2022 18:25:47 +0100 Subject: [PATCH 3/6] feat: add copy button --- .../javigaralva/password-generator-app.tsx | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/components/javigaralva/password-generator-app.tsx b/src/components/javigaralva/password-generator-app.tsx index e5782b3a2..1d97d0789 100644 --- a/src/components/javigaralva/password-generator-app.tsx +++ b/src/components/javigaralva/password-generator-app.tsx @@ -6,6 +6,10 @@ export default function Main() { const { passwordLength, setPasswordLength, password, generatePassword, replaceCharacterAt } = usePasswordGenerator() + const handleCopy = () => { + navigator.clipboard.writeText(password) + } + return (
@@ -16,12 +20,20 @@ export default function Main() {
- +
+ + +
Date: Thu, 10 Nov 2022 18:49:07 +0100 Subject: [PATCH 4/6] refactor: add button component --- .../javigaralva/components/button.tsx | 10 +++++ .../javigaralva/password-generator-app.tsx | 39 +++++++++++++------ 2 files changed, 37 insertions(+), 12 deletions(-) create mode 100644 src/components/javigaralva/components/button.tsx diff --git a/src/components/javigaralva/components/button.tsx b/src/components/javigaralva/components/button.tsx new file mode 100644 index 000000000..46c5c346f --- /dev/null +++ b/src/components/javigaralva/components/button.tsx @@ -0,0 +1,10 @@ +export function Button({ children, className = '', onClick }) { + return ( + + ) +} diff --git a/src/components/javigaralva/password-generator-app.tsx b/src/components/javigaralva/password-generator-app.tsx index 1d97d0789..42943066a 100644 --- a/src/components/javigaralva/password-generator-app.tsx +++ b/src/components/javigaralva/password-generator-app.tsx @@ -1,3 +1,4 @@ +import { Button } from './components/Button' import { ColorizedPassword } from './components/colorized-password' import { MAX_PASSWORD_LENGTH, MIN_PASSWORD_LENGTH } from './constants/constants' import { usePasswordGenerator } from './hooks/use-password-generator' @@ -21,18 +22,8 @@ export default function Main() {
- - + +
) } + +function GenerateButton({ onClick }) { + return ( + + ) +} + +function CopyButton({ onClick }) { + return ( + + ) +} From c5cfad7f2636e6d8bd84e40a10d6d071e0a1e169 Mon Sep 17 00:00:00 2001 From: javigaralva Date: Thu, 10 Nov 2022 18:49:40 +0100 Subject: [PATCH 5/6] refactor: add password slider internal component --- .../javigaralva/password-generator-app.tsx | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/components/javigaralva/password-generator-app.tsx b/src/components/javigaralva/password-generator-app.tsx index 42943066a..bfcb63209 100644 --- a/src/components/javigaralva/password-generator-app.tsx +++ b/src/components/javigaralva/password-generator-app.tsx @@ -25,12 +25,8 @@ export default function Main() {
- setPasswordLength(Number(e.target.value))} />
@@ -42,6 +38,18 @@ export default function Main() { ) } +function PasswordLengthSlider({ passwordLength, onChange }) { + return ( + + ) +} function GenerateButton({ onClick }) { return (