Skip to content
276 changes: 276 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
"node": "16"
},
"dependencies": {
"@reduxjs/toolkit": "^1.8.1",
"@testing-library/jest-dom": "^5.16.1",
"@testing-library/react": "^12.1.2",
"@testing-library/user-event": "^13.5.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-redux": "^8.0.1",
"react-scripts": "4.0.3",
"redux": "^4.2.0",
"web-vitals": "^2.1.4"
},
"scripts": {
Expand Down
21 changes: 5 additions & 16 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,12 @@
import logo from './logo.svg';
import './App.css';
import GeneratedPassword from './components/GeneratedPassword';
import Settings from './components/Settings';

function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
<div>
<GeneratedPassword />
<Settings />
</div>
);
}
Expand Down
26 changes: 26 additions & 0 deletions src/components/GenerateButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import getPassword from '../helpers/generatePassword';
import { savePassword } from '../redux/password/passwordSlice';

function GenerateButton() {
const dispatch = useDispatch()
const length = useSelector((state) => state.password.settings.length);
const settings = useSelector((state) => state.password.settings);

return (
<div>
<button
type="button"
onClick={ () => {
const generatedPassword = getPassword(Number(length), settings);
return dispatch(savePassword(generatedPassword));
} }
>
Generate Password
</button>
</div>
);
}

export default GenerateButton;
15 changes: 15 additions & 0 deletions src/components/GeneratedPassword.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import { useSelector } from 'react-redux';


function GeneratedPassword() {
const password = useSelector((state) => state.password.password);

return (
<div>
<h2>{ password }</h2>
</div>
);
}

export default GeneratedPassword;
34 changes: 34 additions & 0 deletions src/components/LengthRange.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { handleSettings } from '../redux/password/passwordSlice';

function LengthRange() {
const dispatch = useDispatch();
const length = useSelector((state) => state.password.settings.length);

function handleRangeChange(event) {
const { target: { name, value } } = event;
const actionPayload = {
settingName: name,
settingOption: value,
};
dispatch(handleSettings(actionPayload));
};

return(
<label>
length: { length }
<input
defaultValue="4"
type="range"
id="length-range"
min="4"
max="32"
name="length"
onChange={ handleRangeChange }
/>
</label>
);
}

export default LengthRange;
19 changes: 19 additions & 0 deletions src/components/Settings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';
import GenerateButton from './GenerateButton';
import LengthRange from './LengthRange';
import SettingsButtons from './SettingsButtons';

function Settings() {
return(
<div>
<SettingsButtons setting="Include Uppercase" />
<SettingsButtons setting="Include Lowercase" />
<SettingsButtons setting="Include Numbers" />
<SettingsButtons setting="Include Symbols" />
<LengthRange />
<GenerateButton />
</div>
);
}

export default Settings;
34 changes: 34 additions & 0 deletions src/components/SettingsButtons.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import react from 'react';
import { useDispatch } from 'react-redux';
import { handleSettings } from '../redux/password/passwordSlice';

function SettingsButtons({ setting }) {
const dispatch = useDispatch();

function handleCheckboxChange(event) {
const { target: { name, checked } } = event;
const actionPayload = {
settingName: name,
settingOption: checked,
};
dispatch(handleSettings(actionPayload));
};

return (
<div>
<label
htmlFor={ `switch-${setting}` }
>
<input
type="checkbox"
id={ `switch-${setting}` }
name={ setting.replace(' ', '') }
onChange={ handleCheckboxChange }
/>
{ setting }
</label>
</div >
);
}

export default SettingsButtons;
29 changes: 29 additions & 0 deletions src/helpers/generatePassword.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const lowercaseAlphabet = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];

const uppercaseAlphabet = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
];

const numbers = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'];

const symbols = ['!', '@', '#', '$', '%', '¨', '&', '*', '-', '_', '+', '='];

function getPassword(passwordLength, settings) {
const password = [];
const characters = {
numbers: settings.IncludeNumbers ? numbers : [],
uppercase: settings.IncludeUppercase ? uppercaseAlphabet : [],
lowercase: settings.IncludeLowercase ? lowercaseAlphabet : [],
symbols: settings.IncludeSymbols ? symbols : [],
};

const passwordCharacters = Object.values(characters).flat();
for (let index = 0; index < passwordLength; index += 1) {
const randomIndex = Math.floor(Math.random() * passwordCharacters.length);
password.push(passwordCharacters[randomIndex]);
}
return password;
}

export default getPassword;
10 changes: 7 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import store from './redux/store/store';
import { Provider } from 'react-redux';

ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
<Provider store={ store }>
<React.StrictMode>
<App />
</React.StrictMode>
</Provider>,
document.getElementById('root')
);

Expand Down
30 changes: 30 additions & 0 deletions src/redux/password/passwordSlice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { createSlice } from '@reduxjs/toolkit';

const initialState = {
password: '',
settings: {
IncludeNumbers: false,
IncludeLowercase: false,
IncludeUppercase: false,
IncludeSymbols: false,
length: 4,
},
};

const passwordSlice = createSlice({
name: 'password',
initialState,
reducers: {
savePassword: (state, action) => {
state.password = action.payload;
},
handleSettings: (state, action) => {
const { payload: { settingName, settingOption } } = action;
state.settings[settingName] = settingOption;
},
},
});

export const { savePassword, handleSettings } = passwordSlice.actions;

export default passwordSlice.reducer;
9 changes: 9 additions & 0 deletions src/redux/store/store.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { configureStore } from '@reduxjs/toolkit';
import passwordReducer from '../password/passwordSlice';

const store = configureStore({
reducer: {
password: passwordReducer },
});

export default store;