password manager
This commit is contained in:
commit
a1bbe85b1a
|
@ -0,0 +1,3 @@
|
|||
node_modules
|
||||
coverage
|
||||
build
|
|
@ -0,0 +1,60 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"extends": ["react-app", "airbnb", "prettier"],
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
},
|
||||
"ecmaVersion": 12,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["prettier"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["styledComponents.js"],
|
||||
"rules": {
|
||||
"import/prefer-default-export": "off"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"react/jsx-filename-extension": [1, {"extensions": [".js", ".jsx"]}],
|
||||
"react/state-in-constructor": "off",
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"react/jsx-uses-react": "off",
|
||||
"no-console": "off",
|
||||
"react/prop-types": "off",
|
||||
"jsx-a11y/label-has-associated-control": [
|
||||
2,
|
||||
{
|
||||
"labelAttributes": ["htmlFor"]
|
||||
}
|
||||
],
|
||||
"jsx-a11y/click-events-have-key-events": 0,
|
||||
"jsx-a11y/no-noninteractive-element-interactions": [
|
||||
"off",
|
||||
{
|
||||
"handlers": ["onClick"]
|
||||
}
|
||||
],
|
||||
"react/prefer-stateless-function": [
|
||||
0,
|
||||
{
|
||||
"ignorePureComponents": true
|
||||
}
|
||||
],
|
||||
"no-unused-vars": "warn",
|
||||
"jsx-a11y/alt-text": 1,
|
||||
"react/no-unused-state": "warn",
|
||||
"react/button-has-type": "warn",
|
||||
"react/no-unescaped-entities": "warn",
|
||||
"react/jsx-props-no-spreading": "off",
|
||||
"operator-assignment": ["warn", "always"],
|
||||
"radix": "off"
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
* text=auto eol=lf
|
|
@ -0,0 +1,26 @@
|
|||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
.idea/
|
||||
.eslintcache
|
||||
.vscode/
|
||||
.results
|
|
@ -0,0 +1,3 @@
|
|||
registry=https://registry.npmjs.org/
|
||||
package-lock=true
|
||||
save-exact=true
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"0 debug pnpm:scope": {
|
||||
"selected": 1
|
||||
},
|
||||
"1 error pnpm": {
|
||||
"code": "ELIFECYCLE",
|
||||
"errno": "ENOENT",
|
||||
"syscall": "spawn",
|
||||
"file": "sh",
|
||||
"pkgid": "password-manager@1.0.0",
|
||||
"stage": "start",
|
||||
"script": "react-scripts start",
|
||||
"pkgname": "password-manager",
|
||||
"err": {
|
||||
"name": "pnpm",
|
||||
"message": "password-manager@1.0.0 start: `react-scripts start`\nspawn ENOENT",
|
||||
"code": "ELIFECYCLE",
|
||||
"stack": "pnpm: password-manager@1.0.0 start: `react-scripts start`\nspawn ENOENT\n at ChildProcess.<anonymous> (/usr/local/lib/node_modules/pnpm/dist/pnpm.cjs:92384:22)\n at ChildProcess.emit (events.js:400:28)\n at maybeClose (internal/child_process.js:1058:16)\n at Process.ChildProcess._handle.onexit (internal/child_process.js:293:5)"
|
||||
}
|
||||
},
|
||||
"2 warn pnpm:global": " Local package.json exists, but node_modules missing, did you mean to install?"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
node_modules
|
||||
coverage
|
||||
build
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"arrowParens": "avoid",
|
||||
"bracketSpacing": false,
|
||||
"endOfLine": "lf",
|
||||
"htmlWhitespaceSensitivity": "css",
|
||||
"insertPragma": false,
|
||||
"jsxBracketSameLine": false,
|
||||
"jsxSingleQuote": false,
|
||||
"printWidth": 80,
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.md",
|
||||
"options": {
|
||||
"printWidth": 1000
|
||||
}
|
||||
}
|
||||
],
|
||||
"proseWrap": "always",
|
||||
"quoteProps": "as-needed",
|
||||
"requirePragma": false,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "all",
|
||||
"useTabs": false
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
In this project, let's build a **Password Manager** by applying the concepts we have learned till now.
|
||||
|
||||
### Refer to the image below:
|
||||
|
||||
<br/>
|
||||
<div style="text-align: center;">
|
||||
<img src="https://assets.ccbp.in/frontend/content/react-js/passowrd-manager-output-v0.gif" alt="password manager" style="max-width:70%;box-shadow:0 2.8px 2.2px rgba(0, 0, 0, 0.12)">
|
||||
</div>
|
||||
<br/>
|
||||
|
||||
### Design Files
|
||||
|
||||
<details>
|
||||
<summary>Click to view</summary>
|
||||
|
||||
- [Extra Small (Size < 576px) and Small (Size >= 576px) - No Passwords View](https://assets.ccbp.in/frontend/content/react-js/password-manager-no-passwords-sm-output-v2.png)
|
||||
- [Extra Small (Size < 576px) and Small (Size >= 576px) - Masked Passwords View](https://assets.ccbp.in/frontend/content/react-js/password-manager-masked-passwords-sm-output-v2.png)
|
||||
- [Extra Small (Size < 576px) and Small (Size >= 576px) - Show Passwords View](https://assets.ccbp.in/frontend/content/react-js/password-manager-sm-output-v2.png)
|
||||
- [Medium (Size >= 768px), Large (Size >= 992px) and Extra Large (Size >= 1200px) - No Passwords View](https://assets.ccbp.in/frontend/content/react-js/password-manager-no-passwords-lg-output.png)
|
||||
- [Medium (Size >= 768px), Large (Size >= 992px) and Extra Large (Size >= 1200px) - Masked Passwords View](https://assets.ccbp.in/frontend/content/react-js/password-manager-masked-passwords-lg-output.png)
|
||||
- [Medium (Size >= 768px), Large (Size >= 992px) and Extra Large (Size >= 1200px) - Show Passwords View](https://assets.ccbp.in/frontend/content/react-js/password-manager-lg-output.png)
|
||||
|
||||
</details>
|
||||
|
||||
### Set Up Instructions
|
||||
|
||||
<details>
|
||||
<summary>Click to view</summary>
|
||||
|
||||
- Download dependencies by running `npm install`
|
||||
- Start up the app using `npm start`
|
||||
</details>
|
||||
|
||||
### Completion Instructions
|
||||
|
||||
<details>
|
||||
<summary>Functionality to be added</summary>
|
||||
<br/>
|
||||
|
||||
The app must have the following functionalities
|
||||
|
||||
- Initially, the website input, username input, and password input should be empty and [No Passwords View](https://assets.ccbp.in/frontend/content/react-js/password-manager-no-passwords-lg-output.png) should be displayed
|
||||
- When non-empty values are provided for the website, username, and password and the **Add** button is clicked,
|
||||
- A new password item should be added to the list of passwords
|
||||
- The passwords count should be incremented by one
|
||||
- The **stars image** should be displayed in the password items instead of the passwords
|
||||
- The value of the input fields for website, username, and password should be updated to their initial values
|
||||
- When the **Show Password** is checked, then the password should be displayed instead of the **stars image**
|
||||
- When a non-empty value is provided in the search input field, then password items whose website is matched with the search input value irrespective of the case should be displayed
|
||||
- When a non-empty value is provided in the search input field, and if the website of any password item does not match the value given in the search input, then [No Passwords View](https://assets.ccbp.in/frontend/content/react-js/password-manager-no-passwords-lg-output.png) should be displayed
|
||||
- When the delete button of a password item is clicked,
|
||||
- The respective password item should be deleted from the list of passwords
|
||||
- The passwords count should be decremented by one
|
||||
- When all password items are deleted, then [No Passwords View](https://assets.ccbp.in/frontend/content/react-js/password-manager-no-passwords-lg-output.png) should be displayed
|
||||
</details>
|
||||
|
||||
### Important Note
|
||||
|
||||
<details>
|
||||
<summary>Click to view</summary>
|
||||
|
||||
<br/>
|
||||
|
||||
**The following instructions are required for the tests to pass**
|
||||
|
||||
- HTML input element for website should have the placeholder as **Enter Website**
|
||||
- HTML input element for username should have the placeholder as **Enter Username**
|
||||
- HTML input element for password should have the placeholder as **Enter Password**
|
||||
- The delete button for each password item should have the testid as **delete**
|
||||
</details>
|
||||
|
||||
### Resources
|
||||
|
||||
<details>
|
||||
<summary>Image URLs</summary>
|
||||
|
||||
- [https://assets.ccbp.in/frontend/react-js/password-manager-logo-img.png](https://assets.ccbp.in/frontend/react-js/password-manager-logo-img.png) alt should be **app logo**
|
||||
- [https://assets.ccbp.in/frontend/react-js/password-manager-sm-img.png](https://assets.ccbp.in/frontend/react-js/password-manager-sm-img.png) alt should be **password manager**
|
||||
- [https://assets.ccbp.in/frontend/react-js/password-manager-lg-img.png](https://assets.ccbp.in/frontend/react-js/password-manager-lg-img.png) alt should be **password manager**
|
||||
- [https://assets.ccbp.in/frontend/react-js/password-manager-website-img.png](https://assets.ccbp.in/frontend/react-js/password-manager-website-img.png) alt should be **website**
|
||||
- [https://assets.ccbp.in/frontend/react-js/password-manager-username-img.png](https://assets.ccbp.in/frontend/react-js/password-manager-username-img.png) alt should be **username**
|
||||
- [https://assets.ccbp.in/frontend/react-js/password-manager-password-img.png](https://assets.ccbp.in/frontend/react-js/password-manager-password-img.png) alt should be **password**
|
||||
- [https://assets.ccbp.in/frontend/react-js/password-manager-search-img.png](https://assets.ccbp.in/frontend/react-js/password-manager-search-img.png) alt should be **search**
|
||||
- [https://assets.ccbp.in/frontend/react-js/no-passwords-img.png](https://assets.ccbp.in/frontend/react-js/no-passwords-img.png) alt should be **no passwords**
|
||||
- [https://assets.ccbp.in/frontend/react-js/password-manager-stars-img.png](https://assets.ccbp.in/frontend/react-js/password-manager-stars-img.png) alt should be **stars**
|
||||
- [https://assets.ccbp.in/frontend/react-js/password-manager-delete-img.png](https://assets.ccbp.in/frontend/react-js/password-manager-delete-img.png) alt should be **delete**
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Colors</summary>
|
||||
|
||||
<br/>
|
||||
|
||||
<div style="background-color: #9ba9eb; width: 150px; padding: 10px; color: black">Hex: #9ba9eb</div>
|
||||
<div style="background-color: #c3caea; width: 150px; padding: 10px; color: black">Hex: #c3caea</div>
|
||||
<div style="background-color: #5763a5; width: 150px; padding: 10px; color: black">Hex: #5763a5</div>
|
||||
<div style="background-color: #f8fafc; width: 150px; padding: 10px; color: black">Hex: #f8fafc</div>
|
||||
<div style="background-color: #454f84; width: 150px; padding: 10px; color: white">Hex: #454f84</div>
|
||||
<div style="background-color: #0b69ff; width: 150px; padding: 10px; color: black">Hex: #0b69ff</div>
|
||||
<div style="background-color: #94a3b8; width: 150px; padding: 10px; color: black">Hex: #94a3b8</div>
|
||||
<div style="background-color: #b6c3ca; width: 150px; padding: 10px; color: black">Hex: #b6c3ca</div>
|
||||
<div style="background-color: #7683cb; width: 150px; padding: 10px; color: black">Hex: #7683cb</div>
|
||||
<div style="background-color: #f59e0b; width: 150px; padding: 10px; color: black">Hex: #f59e0b</div>
|
||||
<div style="background-color: #10b981; width: 150px; padding: 10px; color: black">Hex: #10b981</div>
|
||||
<div style="background-color: #f97316; width: 150px; padding: 10px; color: black">Hex: #f97316</div>
|
||||
<div style="background-color: #14b8a6; width: 150px; padding: 10px; color: black">Hex: #14b8a6</div>
|
||||
<div style="background-color: #b91c1c; width: 150px; padding: 10px; color: black">Hex: #b91c1c</div>
|
||||
<div style="background-color: #ffffff; width: 150px; padding: 10px; color: black">Hex: #ffffff</div>
|
||||
<div style="background-color: #0ea5e9; width: 150px; padding: 10px; color: black">Hex: #0ea5e9</div>
|
||||
<div style="background-color: #64748b; width: 150px; padding: 10px; color: white">Hex: #64748b</div>
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Font-families</summary>
|
||||
|
||||
- Roboto
|
||||
|
||||
</details>
|
||||
|
||||
> ### _Things to Keep in Mind_
|
||||
>
|
||||
> - All components you implement should go in the `src/components` directory.
|
||||
> - Don't change the component folder names as those are the files being imported into the tests.
|
||||
> - **Do not remove the pre-filled code**
|
||||
> - Want to quickly review some of the concepts you’ve been learning? Take a look at the Cheat Sheets.
|
|
@ -0,0 +1,68 @@
|
|||
{
|
||||
"name": "password-manager",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"engines": {
|
||||
"node": "^10.13 || 12 || 14 || 15",
|
||||
"npm": ">=6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "5.11.9",
|
||||
"@testing-library/react": "11.2.5",
|
||||
"@testing-library/user-event": "12.6.2",
|
||||
"chalk": "4.1.0",
|
||||
"react": "17.0.1",
|
||||
"react-dom": "17.0.1",
|
||||
"uuid": "8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint-config-airbnb": "18.2.1",
|
||||
"eslint-config-prettier": "8.1.0",
|
||||
"eslint-plugin-prettier": "3.3.1",
|
||||
"husky": "4.3.8",
|
||||
"lint-staged": "10.5.4",
|
||||
"npm-run-all": "4.1.5",
|
||||
"prettier": "2.2.1",
|
||||
"react-scripts": "4.0.3"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint --fix src/",
|
||||
"format": "prettier --write \"./src\"",
|
||||
"run-all": "npm-run-all --parallel test lint:fix"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.js": [
|
||||
"npm run lint:fix"
|
||||
],
|
||||
"*.{js, jsx, json, html, css}": [
|
||||
"npm run format"
|
||||
]
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
},
|
||||
"jest": {
|
||||
"collectCoverageFrom": [
|
||||
"src/**/*.js"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"development": [
|
||||
"last 2 chrome versions",
|
||||
"last 2 firefox versions",
|
||||
"last 2 edge versions"
|
||||
],
|
||||
"production": [
|
||||
">1%",
|
||||
"last 4 versions",
|
||||
"Firefox ESR",
|
||||
"not ie < 11"
|
||||
]
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
|
@ -0,0 +1,54 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/img/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Bree+Serif&family=Caveat:wght@400;500;600;700&family=Lobster&family=Monoton&family=Open+Sans:ital,wght@0,300;0,400;0,600;0,700;0,800;1,300;1,400;1,600;1,700;1,800&family=Playfair+Display+SC:ital,wght@0,400;0,700;0,900;1,400;1,700;1,900&family=Playfair+Display:ital,wght@0,400;0,500;0,600;0,700;0,800;0,900;1,400;1,500;1,600;1,700;1,800;1,900&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;1,100;1,300;1,400;1,500;1,700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/img/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
<script type="text/javascript">
|
||||
// hide HRM logs because they're distracting
|
||||
// prettier-ignore
|
||||
const log = console.log;
|
||||
// prettier-ignore
|
||||
console.log = (...args) =>
|
||||
args[0]?.includes?.('[HMR]') ? null : log(...args);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"short_name": "app_short_name",
|
||||
"name": "App_name",
|
||||
"icons": [
|
||||
{
|
||||
"src": "img/favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "img/logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "img/logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
|
@ -0,0 +1,12 @@
|
|||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import PasswordManager from './components/PasswordManager'
|
||||
import './App.css'
|
||||
|
||||
const App = () => <PasswordManager />
|
||||
|
||||
export default App
|
|
@ -0,0 +1,79 @@
|
|||
.userPasswordDetailsList_item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: #5763a5;
|
||||
outline: none;
|
||||
border: 0.1px solid #9ba9eb;
|
||||
border-radius: 3px;
|
||||
padding: 15px;
|
||||
padding-right: 10px;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
min-width: 217px;
|
||||
min-height: 85px;
|
||||
}
|
||||
|
||||
.passwordDetails_container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.initialContainer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #0b69ff;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border-radius: 50%;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.initial{
|
||||
font-family: "Roboto";
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.userPassword_container{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.input_text{
|
||||
font-family: "Roboto";
|
||||
font-size: 12px;
|
||||
color: #ffffff;
|
||||
padding: 2px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.stars_image {
|
||||
width: 100px;
|
||||
height: 15px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.show_password {
|
||||
font-family: "Roboto";
|
||||
font-size: 12px;
|
||||
color: #ffffff;
|
||||
padding: 2px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.delete_button{
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.del_image{
|
||||
height: 20px;
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
import './index.css'
|
||||
|
||||
const PasswordItem = props => {
|
||||
const {userDetails, checked} = props
|
||||
const {id, website, username, password, initialClassName} = userDetails
|
||||
const initial = username ? username[0].toUpperCase() : ''
|
||||
|
||||
const onDelete = () => {
|
||||
const {deleteUserDetails} = props
|
||||
deleteUserDetails(id)
|
||||
}
|
||||
|
||||
return (
|
||||
<li className="userPasswordDetailsList_item">
|
||||
<div className="passwordDetails_container">
|
||||
<div className={`initialContainer ${initialClassName}`}>
|
||||
<p className="initial">{initial}</p>
|
||||
</div>
|
||||
<div className="userPassword_container">
|
||||
<p className="input_text">{website}</p>
|
||||
<p className="input_text">{username}</p>
|
||||
{!checked && (
|
||||
<img
|
||||
src="https://assets.ccbp.in/frontend/react-js/password-manager-stars-img.png"
|
||||
className="stars_image"
|
||||
alt="stars"
|
||||
/>
|
||||
)}
|
||||
{checked && <p className="show_password">{password}</p>}
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" className="delete_button" onClick={onDelete}>
|
||||
<img
|
||||
src="https://assets.ccbp.in/frontend/react-js/password-manager-delete-img.png"
|
||||
alt="delete"
|
||||
className="del_image"
|
||||
/>
|
||||
</button>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
export default PasswordItem
|
|
@ -0,0 +1,327 @@
|
|||
.app_container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #9ba9eb;
|
||||
background-size: cover;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.responsive_container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
border-radius: 10px;
|
||||
}
|
||||
@media screen and (min-width: 768px) {
|
||||
.responsive_container {
|
||||
width: 85%;
|
||||
max-width: 1140px;
|
||||
}
|
||||
}
|
||||
|
||||
.app_logo_container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.app_logo {
|
||||
width: 160px;
|
||||
height: 40px;
|
||||
align-self: flex-start;
|
||||
padding-left: 7.5%;
|
||||
margin-top: 25px;
|
||||
}
|
||||
@media screen and (min-width: 768px) {
|
||||
.app_logo {
|
||||
width: 280px;
|
||||
height: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
.userDetails_container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
background-color: #5763a5;
|
||||
border-radius: 10px;
|
||||
width: 85%;
|
||||
margin-top: 10px;
|
||||
}
|
||||
@media screen and (min-width: 768px) {
|
||||
.userDetails_container {
|
||||
flex-direction: column;
|
||||
width: 85%;
|
||||
max-width: 1140px;
|
||||
}
|
||||
}
|
||||
|
||||
.add_userDetail_container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin-top: 45px;
|
||||
padding-left: 5%;
|
||||
padding-right: 5%;
|
||||
margin-bottom: 45px;
|
||||
}
|
||||
@media screen and (min-width: 768px) {
|
||||
.add_userDetail_container {
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
max-width: 1140px;
|
||||
}
|
||||
}
|
||||
|
||||
.form {
|
||||
background-color: #454f84;
|
||||
border-radius: 15px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-left: 25px;
|
||||
padding-right: 30px;
|
||||
padding-top: 30px;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
|
||||
.form_heading {
|
||||
font-family: "Roboto";
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
color: #ffffff;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.form_input_container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.input_icon_container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
height: 50px;
|
||||
width: 60px;
|
||||
background-color: #ffffff;
|
||||
border-top-left-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
}
|
||||
|
||||
.input_icon {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.form_input {
|
||||
height: 50px;
|
||||
width: 300px;
|
||||
outline: none;
|
||||
border: 0px;
|
||||
border-left: 1px solid #94a3b8;
|
||||
padding-left: 10px;
|
||||
border-top-right-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
}
|
||||
|
||||
.add_button {
|
||||
font-family: "Roboto";
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #ffffff;
|
||||
align-self: flex-end;
|
||||
background-color: #0b69ff;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.password_manager_image {
|
||||
height: 350px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.userDetailsList_container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
background-color: #5763a5;
|
||||
border-radius: 10px;
|
||||
width: 85%;
|
||||
margin-top: 10px;
|
||||
min-height: 500px;
|
||||
}
|
||||
|
||||
.header_container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
padding-left: 45px;
|
||||
padding-right: 45px;
|
||||
}
|
||||
|
||||
.header_heading_container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: 0px;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
.your_passwords_heading {
|
||||
font-family: "Roboto";
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
color: #ffffff;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.count {
|
||||
font-family: "Roboto";
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
color: #ffffff;
|
||||
border: 1px solid #ffffff;
|
||||
border-radius: 10px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.search_container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.search_icon_container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
height: 40px;
|
||||
width: 50px;
|
||||
background-color: #ffffff;
|
||||
border-top-left-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
}
|
||||
|
||||
.search_icon {
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.search_input {
|
||||
font-family: "Roboto";
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
height: 40px;
|
||||
outline: none;
|
||||
border: 0px;
|
||||
border-left: 1px solid #94a3b8;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.breakout_line {
|
||||
border: 1px solid #9ba9eb;
|
||||
width: 93%;
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.check_box_container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
padding-right: 45px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.check_box {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.label_text {
|
||||
font-family: "Roboto";
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.list-item-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
padding: 20px;
|
||||
|
||||
border: 2px solid white;
|
||||
border-radius: 10px;
|
||||
margin-right: 60px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.no-password-image {
|
||||
height: 300px;
|
||||
}
|
||||
.no-results-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.no-passwords-heading {
|
||||
color: white;
|
||||
font-family: 'roboto';
|
||||
}
|
||||
|
||||
.unordered-list-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
list-style-type: none;
|
||||
}
|
||||
.red {
|
||||
background-color: red;
|
||||
}
|
||||
.blue {
|
||||
background-color: blue;
|
||||
}
|
||||
.green {
|
||||
background-color: green;
|
||||
}
|
||||
.violet {
|
||||
background-color: violet;
|
||||
}
|
||||
.indigo {
|
||||
background-color: indigo;
|
||||
}
|
||||
.yellow {
|
||||
background-color: yellow;
|
||||
}
|
|
@ -0,0 +1,250 @@
|
|||
import {Component} from 'react'
|
||||
import {v4} from 'uuid'
|
||||
import PasswordItem from '../PasswordItem'
|
||||
import './index.css'
|
||||
|
||||
const initialContainerBgClassNames = [
|
||||
'red',
|
||||
'blue',
|
||||
'green',
|
||||
'violet',
|
||||
'indigo',
|
||||
'yellow',
|
||||
'cyan',
|
||||
]
|
||||
|
||||
class PasswordManager extends Component {
|
||||
state = {
|
||||
userDetailsList: [],
|
||||
websiteInput: '',
|
||||
usernameInput: '',
|
||||
passwordInput: '',
|
||||
checked: false,
|
||||
searchInput: '',
|
||||
}
|
||||
|
||||
onWebsite = event => {
|
||||
this.setState({
|
||||
websiteInput: event.target.value,
|
||||
})
|
||||
}
|
||||
|
||||
onUsername = event => {
|
||||
this.setState({
|
||||
usernameInput: event.target.value,
|
||||
})
|
||||
}
|
||||
|
||||
onPassword = event => {
|
||||
this.setState({
|
||||
passwordInput: event.target.value,
|
||||
})
|
||||
}
|
||||
|
||||
onSearch = event => {
|
||||
this.setState({searchInput: event.target.value})
|
||||
}
|
||||
|
||||
onAdd = event => {
|
||||
event.preventDefault()
|
||||
const {websiteInput, usernameInput, passwordInput} = this.state
|
||||
const initialBgColorClassNames = `initial_container ${
|
||||
initialContainerBgClassNames[
|
||||
Math.ceil(Math.random() * initialContainerBgClassNames.length - 1)
|
||||
]
|
||||
}`
|
||||
const newUser = {
|
||||
id: v4(),
|
||||
website: websiteInput,
|
||||
username: usernameInput,
|
||||
password: passwordInput,
|
||||
initialClassName: initialBgColorClassNames,
|
||||
}
|
||||
|
||||
this.setState(prevState => ({
|
||||
userDetailsList: [...prevState.userDetailsList, newUser],
|
||||
websiteInput: '',
|
||||
usernameInput: '',
|
||||
passwordInput: '',
|
||||
searchInput: '',
|
||||
}))
|
||||
}
|
||||
|
||||
onCheckBox = () => {
|
||||
this.setState(prevState => ({
|
||||
checked: !prevState.checked,
|
||||
}))
|
||||
}
|
||||
|
||||
getSearchResults = () => {
|
||||
const {searchInput, userDetailsList} = this.state
|
||||
const searchResults = userDetailsList.filter(eachApp =>
|
||||
eachApp.appName.toLowerCase().includes(searchInput.toLowerCase()),
|
||||
)
|
||||
return searchResults
|
||||
}
|
||||
|
||||
deleteUserDetails = Id => {
|
||||
const {userDetailsList} = this.state
|
||||
this.setState({
|
||||
userDetailsList: userDetailsList.filter(
|
||||
userDetails => userDetails.id !== Id,
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
userDetailsList,
|
||||
websiteInput,
|
||||
usernameInput,
|
||||
passwordInput,
|
||||
checked,
|
||||
searchInput,
|
||||
} = this.state
|
||||
const searchedResult = userDetailsList.filter(eachObject =>
|
||||
eachObject.website.toLowerCase().includes(searchInput.toLowerCase()),
|
||||
)
|
||||
let resultView
|
||||
if (userDetailsList.length === 0 || searchedResult.length === 0) {
|
||||
resultView = (
|
||||
<div className="no-results-container">
|
||||
<img
|
||||
className="no-password-image"
|
||||
alt="no passwords"
|
||||
src="https://assets.ccbp.in/frontend/react-js/no-passwords-img.png"
|
||||
/>
|
||||
<p className="no-passwords-heading">No Passwords</p>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
resultView = (
|
||||
<ul className="unordered-list-container">
|
||||
{searchedResult.map(eachObject => (
|
||||
<PasswordItem
|
||||
key={eachObject.id}
|
||||
userDetails={eachObject}
|
||||
checked={checked}
|
||||
deleteUserDetails={this.deleteUserDetails}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="app_container">
|
||||
<div className="responsive_container">
|
||||
<img
|
||||
src="https://assets.ccbp.in/frontend/react-js/password-manager-logo-img.png"
|
||||
alt="app logo"
|
||||
className="app_logo"
|
||||
/>
|
||||
<div className="userDetails_container">
|
||||
<div className="add_userDetail_container">
|
||||
<form className="form" onSubmit={this.onAdd}>
|
||||
<h1 className="form_heading">Add New Passwords</h1>
|
||||
<div className="form_input_container">
|
||||
<div className="input_icon_container">
|
||||
<img
|
||||
src="https://assets.ccbp.in/frontend/react-js/password-manager-website-img.png"
|
||||
alt="website"
|
||||
className="input_icon"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Enter Website"
|
||||
className="form_input"
|
||||
value={websiteInput}
|
||||
onChange={this.onWebsite}
|
||||
/>
|
||||
</div>
|
||||
<div className="form_input_container">
|
||||
<div className="input_icon_container">
|
||||
<img
|
||||
src="https://assets.ccbp.in/frontend/react-js/password-manager-username-img.png"
|
||||
alt="username"
|
||||
className="input_icon"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Enter Username"
|
||||
className="form_input"
|
||||
value={usernameInput}
|
||||
onChange={this.onUsername}
|
||||
/>
|
||||
</div>
|
||||
<div className="form_input_container">
|
||||
<div className="input_icon_container">
|
||||
<img
|
||||
src="https://assets.ccbp.in/frontend/react-js/password-manager-password-img.png"
|
||||
alt="password"
|
||||
className="input_icon"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Enter Password"
|
||||
className="form_input"
|
||||
value={passwordInput}
|
||||
onChange={this.onPassword}
|
||||
/>
|
||||
</div>
|
||||
<button type="submit" className="add_button">
|
||||
Add
|
||||
</button>
|
||||
</form>
|
||||
<img
|
||||
src="https://assets.ccbp.in/frontend/react-js/password-manager-lg-img.png"
|
||||
alt="password manager"
|
||||
className="password_manager_image"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="userDetailsList_container">
|
||||
<div className="header_container">
|
||||
<div className="header_heading_container">
|
||||
<h1 className="your_passwords_heading">Your Passwords</h1>
|
||||
<p className="count">{userDetailsList.length}</p>
|
||||
</div>
|
||||
<div className="search_container">
|
||||
<div className="search_icon_container">
|
||||
<img
|
||||
src="https://assets.ccbp.in/frontend/react-js/password-manager-search-img.png"
|
||||
alt="search"
|
||||
className="search_icon"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
type="search"
|
||||
placeholder="Search"
|
||||
className="search_input"
|
||||
value={searchInput}
|
||||
onChange={this.onSearch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<hr className="breakout_line" />
|
||||
<div className="check_box_container">
|
||||
<input
|
||||
id="checkBox"
|
||||
type="checkbox"
|
||||
className="check_box"
|
||||
value={checked}
|
||||
onChange={this.onCheckBox}
|
||||
/>
|
||||
<label htmlFor="checkBox" className="label_text">
|
||||
Show Passwords
|
||||
</label>
|
||||
</div>
|
||||
<div className="userPasswordDetailsList">{resultView}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default PasswordManager
|
|
@ -0,0 +1,10 @@
|
|||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import App from './App'
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root'),
|
||||
)
|
|
@ -0,0 +1,6 @@
|
|||
/* eslint-disable */
|
||||
|
||||
import '@testing-library/jest-dom'
|
||||
import {configure} from '@testing-library/react'
|
||||
|
||||
configure({testIdAttribute: 'testid'})
|
Loading…
Reference in New Issue