├── public
├── favicon.ico
├── manifest.json
└── index.html
├── src
├── index.js
├── components
│ ├── PasswordList.js
│ ├── Password.js
│ └── Settings.js
├── utility
│ └── password-generator.js
└── App.js
├── README.md
├── .gitignore
└── package.json
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luizbatanero/password-generator-react/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 | import 'semantic-ui-css/semantic.min.css';
5 |
6 | ReactDOM.render(, document.getElementById('root'));
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Password Generator
2 | :lock: Password generator built with React and SemanticUI
3 |
4 | ### [Live Demo](https://password-generator-react.netlify.com/)
5 |
6 | ## Running Locally
7 |
8 | ```sh
9 | npm install
10 | npm start
11 | ```
12 |
13 | Runs the app in development mode.
14 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
15 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/src/components/PasswordList.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Password from './Password';
3 | import { Loader } from 'semantic-ui-react';
4 |
5 | const PasswordList = ({ passwords, isLoading }) => {
6 | if (isLoading) {
7 | return (
8 |
14 | );
15 | }
16 | return (
17 |
18 | {passwords.map((password) => (
19 |
20 | ))}
21 |
22 | );
23 | };
24 |
25 | export default PasswordList;
26 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "password-generator-react",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.8.6",
7 | "react-copy-to-clipboard": "^5.0.1",
8 | "react-dom": "^16.8.6",
9 | "react-scripts": "3.0.1",
10 | "react-semantic-ui-range": "^0.7.0",
11 | "semantic-ui-css": "^2.4.1",
12 | "semantic-ui-react": "^0.87.1",
13 | "zxcvbn": "^4.4.2"
14 | },
15 | "scripts": {
16 | "start": "react-scripts start",
17 | "build": "react-scripts build",
18 | "test": "react-scripts test",
19 | "eject": "react-scripts eject"
20 | },
21 | "eslintConfig": {
22 | "extends": "react-app"
23 | },
24 | "browserslist": {
25 | "production": [
26 | ">0.2%",
27 | "not dead",
28 | "not op_mini all"
29 | ],
30 | "development": [
31 | "last 1 chrome version",
32 | "last 1 firefox version",
33 | "last 1 safari version"
34 | ]
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/utility/password-generator.js:
--------------------------------------------------------------------------------
1 | function getCharactersFromSettings({
2 | lowerCase,
3 | upperCase,
4 | numbers,
5 | symbols,
6 | allowedSymbols
7 | }) {
8 | let characters = [];
9 |
10 | if (lowerCase) characters.push('abcdefghijklmnopqrstuvwxyz');
11 | if (upperCase) characters.push('ABCDEFGHIJKLMNOPQRSTUVWXYZ');
12 | if (numbers) characters.push('0123456789');
13 | if (symbols) characters.push(allowedSymbols);
14 |
15 | return characters.join('');
16 | }
17 |
18 | function randomPassword(characters, { length, lowerCase, upperCase }) {
19 | let password = [];
20 |
21 | while (password.length < length) {
22 | const randomIndex = Math.floor(Math.random() * characters.length);
23 | password.push(characters[randomIndex]);
24 | }
25 |
26 | return password.join('');
27 | }
28 |
29 | export const generatePasswords = settings => {
30 | const characters = getCharactersFromSettings(settings);
31 |
32 | let passwords = [];
33 |
34 | while (passwords.length < settings.amount) {
35 | passwords.push(randomPassword(characters, settings));
36 | }
37 |
38 | return passwords;
39 | };
40 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Settings from './components/Settings';
3 | import PasswordList from './components/PasswordList';
4 | import { generatePasswords } from './utility/password-generator';
5 | import { Grid, Header } from 'semantic-ui-react';
6 |
7 | class App extends React.Component {
8 | state = {
9 | passwords: [],
10 | isLoading: false
11 | };
12 |
13 | handleGenerate = settings => {
14 | this.setState({ isLoading: true }, () => {
15 | setTimeout(() => {
16 | this.setState({
17 | passwords: generatePasswords(settings),
18 | isLoading: false
19 | });
20 | }, 300);
21 | });
22 | };
23 |
24 | render() {
25 | return (
26 |
29 |
30 |
31 |
32 | Password Generator
33 |
34 |
35 |
39 |
40 |
41 |
42 | );
43 | }
44 | }
45 |
46 | export default App;
47 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
22 | Password Generator
23 |
24 |
25 |
26 |
27 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/components/Password.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Card, Button, Popup, Progress } from 'semantic-ui-react';
3 | import { CopyToClipboard } from 'react-copy-to-clipboard';
4 | import zxcvbn from 'zxcvbn';
5 |
6 | const strengthColors = ['red', 'orange', 'yellow', 'olive', 'teal'];
7 |
8 | function charColor(char) {
9 | if (char.match(/\d/)) {
10 | return 'teal';
11 | } else if (char.match(/[^a-zA-Z0-9]/)) {
12 | return 'brown';
13 | } else {
14 | return 'black';
15 | }
16 | }
17 |
18 | const Password = ({ password }) => {
19 | const [copied, setCopied] = useState(false);
20 | const passwordStrength = zxcvbn(password).score;
21 |
22 | return (
23 |
24 |
25 | {password.split('').map((char, index) => (
26 |
30 | {char}
31 |
32 | ))}
33 | setCopied(true)}>
40 |
45 |
46 | }
47 | onClose={() => setCopied(false)}
48 | />
49 |
50 |
51 |
57 |
58 |
59 | );
60 | };
61 |
62 | export default Password;
63 |
--------------------------------------------------------------------------------
/src/components/Settings.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Slider } from 'react-semantic-ui-range';
3 | import {
4 | Segment,
5 | Button,
6 | Icon,
7 | Form,
8 | Label,
9 | Checkbox
10 | } from 'semantic-ui-react';
11 |
12 | class Settings extends React.PureComponent {
13 | state = {
14 | length: 14,
15 | lowerCase: true,
16 | upperCase: true,
17 | numbers: true,
18 | symbols: true,
19 | allowedSymbols: '@!*_-:=#/{}[]',
20 | amount: 3
21 | };
22 |
23 | handleChange = ({ target }) => {
24 | const invalidSymbol =
25 | target.name === 'allowedSymbols' && target.value.match(/[a-zA-Z0-9\s]/);
26 |
27 | if (!invalidSymbol) {
28 | this.setState({ [target.name]: target.value });
29 | }
30 | };
31 |
32 | handleCheckbox = (e, { name, checked }) => {
33 | this.setState({ [name]: checked });
34 | };
35 |
36 | componentDidMount() {
37 | this.props.handleGenerate(this.state);
38 | }
39 |
40 | componentDidUpdate() {
41 | this.props.handleGenerate(this.state);
42 | }
43 |
44 | render() {
45 | const {
46 | symbols,
47 | allowedSymbols,
48 | lowerCase,
49 | upperCase,
50 | numbers,
51 | length,
52 | amount
53 | } = this.state;
54 |
55 | if (symbols && !allowedSymbols.length) {
56 | this.setState({ symbols: false });
57 | }
58 | if (!lowerCase && !upperCase && !symbols && !numbers) {
59 | this.setState({ lowerCase: true });
60 | }
61 |
62 | return (
63 |
64 |
65 | this.setState({ length: value })
74 | }}
75 | style={{ inner: { margin: 0 } }}
76 | />
77 |
81 |
82 |
89 |
96 |
103 |
112 | }
113 | name="allowedSymbols"
114 | value={this.state.allowedSymbols}
115 | onChange={this.handleChange}
116 | />
117 |
118 | this.setState({ amount: value })
127 | }}
128 | style={{ inner: { margin: 0 } }}
129 | />
130 |
134 |
135 |
143 |
144 | );
145 | }
146 | }
147 |
148 | export default Settings;
149 |
--------------------------------------------------------------------------------