diff --git a/part5/bloglist-frontend/.eslintignore b/part5/bloglist-frontend/.eslintignore new file mode 100644 index 0000000..f7d4d65 --- /dev/null +++ b/part5/bloglist-frontend/.eslintignore @@ -0,0 +1,3 @@ +node_modules +build +.eslintrc.js \ No newline at end of file diff --git a/part5/bloglist-frontend/.eslintrc.js b/part5/bloglist-frontend/.eslintrc.js new file mode 100644 index 0000000..7f4790d --- /dev/null +++ b/part5/bloglist-frontend/.eslintrc.js @@ -0,0 +1,35 @@ +/* eslint-env node */ +module.exports = { + env: { + browser: true, + es6: true, + 'jest/globals': true, + }, + extends: ['eslint:recommended', 'plugin:react/recommended'], + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + ecmaVersion: 2018, + sourceType: 'module', + }, + plugins: ['react', 'jest'], + rules: { + indent: ['error', 2], + 'linebreak-style': ['error', 'unix'], + quotes: ['error', 'single'], + semi: ['error', 'never'], + eqeqeq: 'error', + 'no-trailing-spaces': 'error', + 'object-curly-spacing': ['error', 'always'], + 'arrow-spacing': ['error', { before: true, after: true }], + 'no-console': 0, + 'react/prop-types': 0, + 'react/react-in-jsx-scope': 'off', + }, + settings: { + react: { + version: 'detect', + }, + }, +} diff --git a/part5/bloglist-frontend/package-lock.json b/part5/bloglist-frontend/package-lock.json index 4d22b66..006d902 100644 --- a/part5/bloglist-frontend/package-lock.json +++ b/part5/bloglist-frontend/package-lock.json @@ -12,10 +12,14 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "axios": "^1.2.3", + "prop-types": "^15.8.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" + }, + "devDependencies": { + "eslint-plugin-jest": "^27.2.2" } }, "node_modules/@adobe/css-tools": { @@ -2125,6 +2129,20 @@ "postcss-selector-parser": "^6.0.10" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, "node_modules/@eslint/eslintrc": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", @@ -4189,11 +4207,11 @@ } }, "node_modules/@typescript-eslint/experimental-utils": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.49.0.tgz", - "integrity": "sha512-veLpCJLYn44Fru7mSvi2doxQMzMCOFSDYdMUQhAzaH1vFYq2RVNpecZ8d18Wh6UMv07yahXkiv/aShWE48iE9Q==", + "version": "5.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.60.0.tgz", + "integrity": "sha512-ovid3u7CNBrr0Ct35LUPkNYH4e+z4Kc6dPfSG99oMmH9SfoEoefq09uSnJI4mUb/UM7a/peVM03G+MzLxrD16g==", "dependencies": { - "@typescript-eslint/utils": "5.49.0" + "@typescript-eslint/utils": "5.60.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -4206,6 +4224,121 @@ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/scope-manager": { + "version": "5.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.60.0.tgz", + "integrity": "sha512-hakuzcxPwXi2ihf9WQu1BbRj1e/Pd8ZZwVTG9kfbxAMZstKz8/9OoexIwnmLzShtsdap5U/CoQGRCWlSuPbYxQ==", + "dependencies": { + "@typescript-eslint/types": "5.60.0", + "@typescript-eslint/visitor-keys": "5.60.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/types": { + "version": "5.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.60.0.tgz", + "integrity": "sha512-ascOuoCpNZBccFVNJRSC6rPq4EmJ2NkuoKnd6LDNyAQmdDnziAtxbCGWCbefG1CNzmDvd05zO36AmB7H8RzKPA==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.60.0.tgz", + "integrity": "sha512-R43thAuwarC99SnvrBmh26tc7F6sPa2B3evkXp/8q954kYL6Ro56AwASYWtEEi+4j09GbiNAHqYwNNZuNlARGQ==", + "dependencies": { + "@typescript-eslint/types": "5.60.0", + "@typescript-eslint/visitor-keys": "5.60.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/utils": { + "version": "5.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.60.0.tgz", + "integrity": "sha512-ba51uMqDtfLQ5+xHtwlO84vkdjrqNzOnqrnwbMHMRY8Tqeme8C2Q8Fc7LajfGR+e3/4LoYiWXUM6BpIIbHJ4hQ==", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.60.0", + "@typescript-eslint/types": "5.60.0", + "@typescript-eslint/typescript-estree": "5.60.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.60.0.tgz", + "integrity": "sha512-wm9Uz71SbCyhUKgcaPRauBdTegUyY/ZWl8gLwD/i/ybJqscrrdVSFImpvUz16BLPChIeKBK5Fa9s6KDQjsjyWw==", + "dependencies": { + "@typescript-eslint/types": "5.60.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "engines": { + "node": ">=4.0" + } + }, "node_modules/@typescript-eslint/parser": { "version": "5.49.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.49.0.tgz", @@ -7069,6 +7202,29 @@ "eslint": "^8.0.0" } }, + "node_modules/eslint-config-react-app/node_modules/eslint-plugin-jest": { + "version": "25.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-25.7.0.tgz", + "integrity": "sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ==", + "dependencies": { + "@typescript-eslint/experimental-utils": "^5.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^4.0.0 || ^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } + } + }, "node_modules/eslint-import-resolver-node": { "version": "0.3.7", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", @@ -7184,18 +7340,20 @@ } }, "node_modules/eslint-plugin-jest": { - "version": "25.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-25.7.0.tgz", - "integrity": "sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ==", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.2.2.tgz", + "integrity": "sha512-euzbp06F934Z7UDl5ZUaRPLAc9MKjh0rMPERrHT7UhlCEwgb25kBj37TvMgWeHZVkR5I9CayswrpoaqZU1RImw==", + "dev": true, "dependencies": { - "@typescript-eslint/experimental-utils": "^5.0.0" + "@typescript-eslint/utils": "^5.10.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^4.0.0 || ^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "@typescript-eslint/eslint-plugin": "^5.0.0", + "eslint": "^7.0.0 || ^8.0.0", + "jest": "*" }, "peerDependenciesMeta": { "@typescript-eslint/eslint-plugin": { @@ -18486,6 +18644,14 @@ "integrity": "sha512-zJ6hb3FDgBbO8d2e83vg6zq7tNvDqSq9RwdwfzJ8tdm9JHNvANq2fqwyRn6mlpUb7CwTs5ILdUrGwi9Gk4vY5w==", "requires": {} }, + "@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "requires": { + "eslint-visitor-keys": "^3.3.0" + } + }, "@eslint/eslintrc": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", @@ -20029,11 +20195,79 @@ } }, "@typescript-eslint/experimental-utils": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.49.0.tgz", - "integrity": "sha512-veLpCJLYn44Fru7mSvi2doxQMzMCOFSDYdMUQhAzaH1vFYq2RVNpecZ8d18Wh6UMv07yahXkiv/aShWE48iE9Q==", + "version": "5.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.60.0.tgz", + "integrity": "sha512-ovid3u7CNBrr0Ct35LUPkNYH4e+z4Kc6dPfSG99oMmH9SfoEoefq09uSnJI4mUb/UM7a/peVM03G+MzLxrD16g==", "requires": { - "@typescript-eslint/utils": "5.49.0" + "@typescript-eslint/utils": "5.60.0" + }, + "dependencies": { + "@typescript-eslint/scope-manager": { + "version": "5.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.60.0.tgz", + "integrity": "sha512-hakuzcxPwXi2ihf9WQu1BbRj1e/Pd8ZZwVTG9kfbxAMZstKz8/9OoexIwnmLzShtsdap5U/CoQGRCWlSuPbYxQ==", + "requires": { + "@typescript-eslint/types": "5.60.0", + "@typescript-eslint/visitor-keys": "5.60.0" + } + }, + "@typescript-eslint/types": { + "version": "5.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.60.0.tgz", + "integrity": "sha512-ascOuoCpNZBccFVNJRSC6rPq4EmJ2NkuoKnd6LDNyAQmdDnziAtxbCGWCbefG1CNzmDvd05zO36AmB7H8RzKPA==" + }, + "@typescript-eslint/typescript-estree": { + "version": "5.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.60.0.tgz", + "integrity": "sha512-R43thAuwarC99SnvrBmh26tc7F6sPa2B3evkXp/8q954kYL6Ro56AwASYWtEEi+4j09GbiNAHqYwNNZuNlARGQ==", + "requires": { + "@typescript-eslint/types": "5.60.0", + "@typescript-eslint/visitor-keys": "5.60.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/utils": { + "version": "5.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.60.0.tgz", + "integrity": "sha512-ba51uMqDtfLQ5+xHtwlO84vkdjrqNzOnqrnwbMHMRY8Tqeme8C2Q8Fc7LajfGR+e3/4LoYiWXUM6BpIIbHJ4hQ==", + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.60.0", + "@typescript-eslint/types": "5.60.0", + "@typescript-eslint/typescript-estree": "5.60.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.60.0.tgz", + "integrity": "sha512-wm9Uz71SbCyhUKgcaPRauBdTegUyY/ZWl8gLwD/i/ybJqscrrdVSFImpvUz16BLPChIeKBK5Fa9s6KDQjsjyWw==", + "requires": { + "@typescript-eslint/types": "5.60.0", + "eslint-visitor-keys": "^3.3.0" + } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" + } } }, "@typescript-eslint/parser": { @@ -22227,6 +22461,16 @@ "eslint-plugin-react": "^7.27.1", "eslint-plugin-react-hooks": "^4.3.0", "eslint-plugin-testing-library": "^5.0.1" + }, + "dependencies": { + "eslint-plugin-jest": { + "version": "25.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-25.7.0.tgz", + "integrity": "sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ==", + "requires": { + "@typescript-eslint/experimental-utils": "^5.0.0" + } + } } }, "eslint-import-resolver-node": { @@ -22322,11 +22566,12 @@ } }, "eslint-plugin-jest": { - "version": "25.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-25.7.0.tgz", - "integrity": "sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ==", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.2.2.tgz", + "integrity": "sha512-euzbp06F934Z7UDl5ZUaRPLAc9MKjh0rMPERrHT7UhlCEwgb25kBj37TvMgWeHZVkR5I9CayswrpoaqZU1RImw==", + "dev": true, "requires": { - "@typescript-eslint/experimental-utils": "^5.0.0" + "@typescript-eslint/utils": "^5.10.0" } }, "eslint-plugin-jsx-a11y": { diff --git a/part5/bloglist-frontend/package.json b/part5/bloglist-frontend/package.json index 9cb8947..9fd6f1c 100644 --- a/part5/bloglist-frontend/package.json +++ b/part5/bloglist-frontend/package.json @@ -7,6 +7,7 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "axios": "^1.2.3", + "prop-types": "^15.8.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-scripts": "5.0.1", @@ -16,7 +17,8 @@ "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", - "eject": "react-scripts eject" + "eject": "react-scripts eject", + "eslint": "eslint ." }, "eslintConfig": { "extends": [ @@ -36,5 +38,8 @@ "last 1 safari version" ] }, - "proxy": "http://localhost:3001/" + "proxy": "http://localhost:3001/", + "devDependencies": { + "eslint-plugin-jest": "^27.2.2" + } } diff --git a/part5/bloglist-frontend/src/App.js b/part5/bloglist-frontend/src/App.js index ddb26c4..e374ae3 100644 --- a/part5/bloglist-frontend/src/App.js +++ b/part5/bloglist-frontend/src/App.js @@ -1,110 +1,110 @@ -import { useState, useEffect, useRef } from "react"; -import Blog from "./components/Blog"; -import { BlogForm } from "./components/BlogForm"; -import Notification from "./components/Notification"; -import blogService from "./services/blogs"; -import loginService from "./services/login"; -import Togglable from "./components/Togglable"; +import { useState, useEffect, useRef } from 'react' +import Blog from './components/Blog' +import { BlogForm } from './components/BlogForm' +import Notification from './components/Notification' +import blogService from './services/blogs' +import loginService from './services/login' +import Togglable from './components/Togglable' const App = () => { - const [blogs, setBlogs] = useState([]); - const [user, setUser] = useState(null); - const [username, setUsername] = useState(""); - const [password, setPassword] = useState(""); - const [errorMessage, setErrorMessage] = useState(null); - const [successMessage, setSuccessMessage] = useState(null); + const [blogs, setBlogs] = useState([]) + const [user, setUser] = useState(null) + const [username, setUsername] = useState('') + const [password, setPassword] = useState('') + const [errorMessage, setErrorMessage] = useState(null) + const [successMessage, setSuccessMessage] = useState(null) const resetInputFields = () => { - setUsername(""); - setPassword(""); - }; + setUsername('') + setPassword('') + } const setNotification = (message, type) => { - if (type === "success") { - setSuccessMessage(message); + if (type === 'success') { + setSuccessMessage(message) setTimeout(() => { - setSuccessMessage(null); - }, 5000); - } else if (type === "error") { - setErrorMessage(message); + setSuccessMessage(null) + }, 5000) + } else if (type === 'error') { + setErrorMessage(message) setTimeout(() => { - setErrorMessage(null); - }, 5000); + setErrorMessage(null) + }, 5000) } - }; + } useEffect(() => { - blogService.getAll().then((blogs) => setBlogs(blogs)); - const loggedInUserLocal = window.localStorage.getItem("loggedInUser"); + blogService.getAll().then((blogs) => setBlogs(blogs)) + const loggedInUserLocal = window.localStorage.getItem('loggedInUser') if (loggedInUserLocal) { - const user = JSON.parse(loggedInUserLocal); - setUser(user); - blogService.setToken(user.token); + const user = JSON.parse(loggedInUserLocal) + setUser(user) + blogService.setToken(user.token) } - }, []); + }, []) const handleLogin = async (event) => { - event.preventDefault(); + event.preventDefault() try { - const user = await loginService.login({ username, password }); - blogService.setToken(user.token); - window.localStorage.setItem("loggedInUser", JSON.stringify(user)); - setUser(user); - resetInputFields(); - setNotification("Login successful", "success"); + const user = await loginService.login({ username, password }) + blogService.setToken(user.token) + window.localStorage.setItem('loggedInUser', JSON.stringify(user)) + setUser(user) + resetInputFields() + setNotification('Login successful', 'success') } catch (exception) { - setNotification("Wrong credentials", "error"); + setNotification('Wrong credentials', 'error') } - }; + } const handleLogout = async (event) => { - event.preventDefault(); + event.preventDefault() try { - setNotification("Logout successful", "success"); - window.localStorage.clear(); - blogService.setToken(null); - setUser(null); - resetInputFields(); + setNotification('Logout successful', 'success') + window.localStorage.clear() + blogService.setToken(null) + setUser(null) + resetInputFields() } catch (exception) { - setNotification("Logout failed", "error"); + setNotification('Logout failed', 'error') } - }; - const newBlogRef = useRef(); + } + const newBlogRef = useRef() const handleNewBlog = (newBlog) => { - newBlogRef.current.toggleVisibility(); + newBlogRef.current.toggleVisibility() blogService .create(newBlog) .then((returnedBlog) => { - setBlogs(blogs.concat(returnedBlog)); - resetInputFields(); + setBlogs(blogs.concat(returnedBlog)) + resetInputFields() setNotification( `A new blog ${returnedBlog.title} by ${returnedBlog.author} at ${returnedBlog.url} added`, - "success" - ); + 'success' + ) }) .catch((error) => { - setNotification("Failed to add new blog: " + error, "error"); - }); - }; + setNotification('Failed to add new blog: ' + error, 'error') + }) + } const handleRemoveBlog = (blog) => { if (window.confirm(`Remove blog ${blog.title} by ${blog.author}?`)) { blogService .remove(blog.id) .then(() => { - setBlogs(blogs.filter((b) => b.id !== blog.id)); + setBlogs(blogs.filter((b) => b.id !== blog.id)) setNotification( `Blog ${blog.title} by ${blog.author} removed`, - "success" - ); + 'success' + ) }) .catch((error) => { - setNotification("Failed to remove blog: " + error, "error"); - }); + setNotification('Failed to remove blog: ' + error, 'error') + }) } - }; + } const loginForm = () => (
@@ -128,7 +128,7 @@ const App = () => {
- ); + ) const blogForm = () => { return ( @@ -148,8 +148,8 @@ const App = () => { /> ))} - ); - }; + ) + } return (
@@ -167,7 +167,7 @@ const App = () => {
{blogForm()}
)}
- ); -}; + ) +} -export default App; +export default App diff --git a/part5/bloglist-frontend/src/components/Blog.js b/part5/bloglist-frontend/src/components/Blog.js index 3b5bd8a..451d0db 100644 --- a/part5/bloglist-frontend/src/components/Blog.js +++ b/part5/bloglist-frontend/src/components/Blog.js @@ -1,30 +1,30 @@ -import { useState } from "react"; -import blogService from "../services/blogs"; -import Togglable from "./Togglable"; +import { useState } from 'react' +import blogService from '../services/blogs' +import Togglable from './Togglable' const Blog = ({ blog, setNotification, removeBlog }) => { const blogStyle = { paddingTop: 10, paddingLeft: 2, - border: "solid", + border: 'solid', borderWidth: 1, marginBottom: 5, - }; + } - const [likes, setLikes] = useState(blog.likes); + const [likes, setLikes] = useState(blog.likes) const handleLike = (blog) => { - blog.likes = blog.likes + 1; + blog.likes = blog.likes + 1 blogService .update(blog.id, blog) .then((returnedBlog) => { - setLikes(returnedBlog.likes); - setNotification(`You liked ${blog.title}`, "success"); + setLikes(returnedBlog.likes) + setNotification(`You liked ${blog.title}`, 'success') }) .catch((error) => { - setNotification(`Like failed. Error: ${error}`, "error"); - }); - }; + setNotification(`Like failed. Error: ${error}`, 'error') + }) + } return (
@@ -43,7 +43,7 @@ const Blog = ({ blog, setNotification, removeBlog }) => {
- ); -}; + ) +} -export default Blog; +export default Blog diff --git a/part5/bloglist-frontend/src/components/BlogForm.js b/part5/bloglist-frontend/src/components/BlogForm.js index 7cb6621..0bcea8a 100644 --- a/part5/bloglist-frontend/src/components/BlogForm.js +++ b/part5/bloglist-frontend/src/components/BlogForm.js @@ -1,25 +1,25 @@ -import { useState } from "react"; +import { useState } from 'react' export const BlogForm = ({ createBlog }) => { - const [title, setTitle] = useState(""); - const [author, setAuthor] = useState(""); - const [urlAddress, setUrlAddress] = useState(""); + const [title, setTitle] = useState('') + const [author, setAuthor] = useState('') + const [urlAddress, setUrlAddress] = useState('') const resetInputFields = () => { - setTitle(""); - setAuthor(""); - setUrlAddress(""); - }; + setTitle('') + setAuthor('') + setUrlAddress('') + } const addBlog = async (event) => { - event.preventDefault(); + event.preventDefault() createBlog({ title: title, author: author, url: urlAddress, - }); - resetInputFields(); - }; + }) + resetInputFields() + } return (
@@ -55,5 +55,5 @@ export const BlogForm = ({ createBlog }) => {
- ); -}; + ) +} diff --git a/part5/bloglist-frontend/src/components/Notification.js b/part5/bloglist-frontend/src/components/Notification.js index e611322..a8e8fc0 100644 --- a/part5/bloglist-frontend/src/components/Notification.js +++ b/part5/bloglist-frontend/src/components/Notification.js @@ -1,18 +1,18 @@ const errorNotification = ({ message }) => { if (message === null) { - return null; + return null } else { - return
{message}
; + return
{message}
} -}; +} const successNotification = ({ message }) => { if (message === null) { - return null; + return null } else { - return
{message}
; + return
{message}
} -}; +} const Notification = ({ errorMessage, successMessage }) => { return ( @@ -20,7 +20,7 @@ const Notification = ({ errorMessage, successMessage }) => { {errorNotification({ message: errorMessage })} {successNotification({ message: successMessage })} - ); -}; + ) +} -export default Notification; +export default Notification diff --git a/part5/bloglist-frontend/src/components/Togglable.js b/part5/bloglist-frontend/src/components/Togglable.js index 5052265..964d65e 100644 --- a/part5/bloglist-frontend/src/components/Togglable.js +++ b/part5/bloglist-frontend/src/components/Togglable.js @@ -1,18 +1,19 @@ -import { useState, forwardRef, useImperativeHandle } from "react"; +import { useState, forwardRef, useImperativeHandle } from 'react' +import PropTypes from 'prop-types' const Togglable = forwardRef((props, refs) => { - const [visible, setVisible] = useState(false); + const [visible, setVisible] = useState(false) - const hideWhenVisible = { display: visible ? "none" : "" }; - const showWhenVisible = { display: visible ? "" : "none" }; + const hideWhenVisible = { display: visible ? 'none' : '' } + const showWhenVisible = { display: visible ? '' : 'none' } const toggleVisibility = () => { - setVisible(!visible); - }; + setVisible(!visible) + } useImperativeHandle(refs, () => { - return { toggleVisibility }; - }); + return { toggleVisibility } + }) return (
@@ -24,7 +25,13 @@ const Togglable = forwardRef((props, refs) => {
- ); -}); + ) +}) -export default Togglable; +Togglable.propTypes = { + buttonLabel: PropTypes.string.isRequired, +} + +Togglable.displayName = 'Togglable' + +export default Togglable diff --git a/part5/bloglist-frontend/src/services/blogs.js b/part5/bloglist-frontend/src/services/blogs.js index c1c338f..3b24580 100644 --- a/part5/bloglist-frontend/src/services/blogs.js +++ b/part5/bloglist-frontend/src/services/blogs.js @@ -1,39 +1,39 @@ -import axios from "axios"; -const baseUrl = "/api/blogs"; +import axios from 'axios' +const baseUrl = '/api/blogs' -let token = null; +let token = null const setToken = (newToken) => { - token = `Bearer ${newToken}`; -}; + token = `Bearer ${newToken}` +} const getAll = async () => { - const request = axios.get(baseUrl); - const response = await request; - return response.data; -}; + const request = axios.get(baseUrl) + const response = await request + return response.data +} const create = async (newObject) => { const config = { headers: { Authorization: token }, - }; + } - const response = await axios.post(baseUrl, newObject, config); - return response.data; -}; + const response = await axios.post(baseUrl, newObject, config) + return response.data +} const update = async (id, newObject) => { - const response = await axios.put(`${baseUrl}/${id}`, newObject); - return response.data; -}; + const response = await axios.put(`${baseUrl}/${id}`, newObject) + return response.data +} const remove = async (id) => { const config = { headers: { Authorization: token }, - }; - const response = await axios.delete(`${baseUrl}/${id}`, config); - return response.data; -}; + } + const response = await axios.delete(`${baseUrl}/${id}`, config) + return response.data +} const blogService = { setToken, @@ -41,6 +41,6 @@ const blogService = { create, update, remove, -}; +} -export default blogService; +export default blogService diff --git a/part5/bloglist-frontend/src/services/login.js b/part5/bloglist-frontend/src/services/login.js index 7543395..f5b4624 100644 --- a/part5/bloglist-frontend/src/services/login.js +++ b/part5/bloglist-frontend/src/services/login.js @@ -1,11 +1,11 @@ -import axios from "axios"; -const baseUrl = "/api/login"; +import axios from 'axios' +const baseUrl = '/api/login' const login = async (credentials) => { - const response = await axios.post(baseUrl, credentials); - return response.data; -}; + const response = await axios.post(baseUrl, credentials) + return response.data +} -const loginService = { login }; +const loginService = { login } -export default loginService; +export default loginService