From 3956f729a53ad1819037741e86f5c1c5dcc5c391 Mon Sep 17 00:00:00 2001 From: River Golden Date: Sat, 10 Jul 2021 23:54:42 +0700 Subject: [PATCH 1/3] river test --- backend/.env.examle | 3 +- backend/controllers/auth.js | 21 + backend/controllers/user.js | 86 ++ backend/index.js | 2 + backend/package-lock.json | 148 ++++ backend/package.json | 6 +- backend/routes/api/auth.js | 13 + backend/routes/api/index.js | 7 + backend/routes/api/user.js | 11 + backend/services/user.js | 82 +- backend/sql/insert.sql | 5 +- backend/sql/schema.sql | 4 + backend/utils/index.js | 7 + backend/validator/index.js | 9 + backend/validator/user.js | 10 + frontend/.eslintcache | 2 +- frontend/.eslintrc | 4 +- frontend/craco.config.js | 10 + frontend/package-lock.json | 775 +++++++++++++++++- frontend/package.json | 23 +- frontend/src/App.jsx | 21 +- .../CheckBoxField/CheckBoxField.css | 11 + .../src/componenets/CheckBoxField/index.jsx | 39 + frontend/src/componenets/FileField/index.jsx | 57 ++ frontend/src/componenets/Profile/index.jsx | 107 ++- frontend/src/componenets/TextField/index.jsx | 25 + frontend/src/index.css | 8 + frontend/src/index.jsx | 1 + frontend/src/pages/profile/index.jsx | 18 +- frontend/src/stores/rootReducer.js | 9 + frontend/src/stores/user/userAction.js | 76 ++ frontend/src/stores/user/userReducer.js | 80 ++ frontend/src/tools/api.js | 9 +- frontend/tailwind.config.js | 16 + 34 files changed, 1659 insertions(+), 46 deletions(-) create mode 100644 backend/controllers/auth.js create mode 100644 backend/controllers/user.js create mode 100644 backend/routes/api/auth.js create mode 100644 backend/routes/api/index.js create mode 100644 backend/routes/api/user.js create mode 100644 backend/utils/index.js create mode 100644 backend/validator/index.js create mode 100644 backend/validator/user.js create mode 100644 frontend/craco.config.js create mode 100644 frontend/src/componenets/CheckBoxField/CheckBoxField.css create mode 100644 frontend/src/componenets/CheckBoxField/index.jsx create mode 100644 frontend/src/componenets/FileField/index.jsx create mode 100644 frontend/src/componenets/TextField/index.jsx create mode 100644 frontend/src/index.css create mode 100644 frontend/src/stores/rootReducer.js create mode 100644 frontend/src/stores/user/userAction.js create mode 100644 frontend/src/stores/user/userReducer.js create mode 100644 frontend/tailwind.config.js diff --git a/backend/.env.examle b/backend/.env.examle index cd3a056..42f376e 100644 --- a/backend/.env.examle +++ b/backend/.env.examle @@ -3,4 +3,5 @@ PG_PASSWORD=coding_test_password PG_HOST=localhost PG_PORT=5432 PG_DATABASE=thm_database -SECRET=test-dev-secret \ No newline at end of file +SECRET=test-dev-secret +JWT_SECRET=ashfjhsauoujfghjkfha \ No newline at end of file diff --git a/backend/controllers/auth.js b/backend/controllers/auth.js new file mode 100644 index 0000000..403fdf6 --- /dev/null +++ b/backend/controllers/auth.js @@ -0,0 +1,21 @@ +const jwt = require('jsonwebtoken'); +const expressJwt = require('express-jwt'); + +const { authenticateUser } = require('../services/user'); + +exports.login = async (req, res) => { + try { + const { email, password } = req.body; + const user = await authenticateUser(email, password); + const token = jwt.sign({ _id: user.id }, process.env.JWT_SECRET, { expiresIn: '1d' }); + res.cookie('token', token, { expiresIn: '1d' }); + user.token = token; + return res.status(200).json(user) + } catch (err) { + res.status(401).json({ message: 'wrong credentials' }); + } +} + +exports.requireSignin = expressJwt({ + secret: process.env.JWT_SECRET, algorithms: ['HS256'] +}); \ No newline at end of file diff --git a/backend/controllers/user.js b/backend/controllers/user.js new file mode 100644 index 0000000..ef9c3e1 --- /dev/null +++ b/backend/controllers/user.js @@ -0,0 +1,86 @@ +const formidable = require('formidable'); +const fs = require('fs'); + +const { getUserById, updateUser, getUserProfilePicture } = require('../services/user'); +const { checkEmail } = require('../utils'); + +exports.update = async (req, res) => { + try { + const user = await getUserById(req.params.id); + if (user) { + let form = new formidable.IncomingForm(); + form.keepExtensions = true; + form.parse(req, async (err, fields, files) => { + if (err) { + return res.status(400).json({ + message: 'Profile could not upload' + }); + } + + const { email, firstname, lastname, phonenumber, city, country, emailalert, smsalert } = fields; + + if (!email || !checkEmail(email)) { + return res.status(401).json({ + message: 'Invalid Email' + }); + } + + const updateData = { + email: email, + first_name: firstname ? firstname : null, + last_name: lastname ? lastname : null, + phone_number: phonenumber ? phonenumber : null, + city: city ? city : null, + country: country ? country : null, + email_alert: emailalert === '1' ? 1 : 0, + sms_alert: smsalert === '1' ? 1 : 0, + id: user.id + }; + + if (files.profile) { + if (files.profile.size > 10000000) { + return res.status(400).json({ + error: 'Profile should be less then 1mb in size' + }); + } + updateData.profile_picture = fs.readFileSync(files.profile.path); + updateData.profile_picture_content_type = files.profile.type; + } + + const updated = await updateUser(updateData); + return res.json({ updated }); + + }); + } else { + return res.status(400).json({ message: err }); + } + } catch (err) { + console.log(err); + return res.status(400).json({ message: err }); + } +} + +exports.picture = async (req, res) => { + try { + const user = await getUserProfilePicture(req.params.id); + if (user.profilepicture) { + res.set('Content-Type', user.profilepicturecontenttype); + return res.send(user.profilepicture); + } + else { + return res.status(404).json({ message: 'Not Found' }); + } + } catch (err) { + console.log(err); + return res.status(404).json({ message: 'Not Found' }); + } +} + +exports.show = async (req, res) => { + try { + const user = await getUserById(req.params.id); + return res.send(user); + } catch (err) { + return res.status(400).json({ message: 'Not Found' }); + } +} diff --git a/backend/index.js b/backend/index.js index 02f8f13..e237aec 100644 --- a/backend/index.js +++ b/backend/index.js @@ -8,6 +8,7 @@ const port = 3002; const app = express(); + app.use((req, res, next) => { res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content, Accept, Content-Type, Authorization'); @@ -23,6 +24,7 @@ app.use( ); app.use(morganMiddleware); app.get('/health', (req, res) => res.send({ message: 'ok' })); +app.use('/api', require('./routes/api')); const server = app.listen(port, () => { console.log(`THM App running on port ${port}.`); diff --git a/backend/package-lock.json b/backend/package-lock.json index bbcd048..fdaa415 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -428,6 +428,11 @@ "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "dev": true }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + }, "axe-core": { "version": "3.5.5", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-3.5.5.tgz", @@ -523,6 +528,11 @@ "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, "buffer-writer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", @@ -853,6 +863,14 @@ "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -1376,6 +1394,38 @@ "vary": "~1.1.2" } }, + "express-jwt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/express-jwt/-/express-jwt-6.0.0.tgz", + "integrity": "sha512-C26y9myRjx7CyhZ+BAT3p+gQyRCoDZ7qo8plCvLDaRT6je6ALIAQknT6XLVQGFKwIy/Ux7lvM2MNap5dt0T7gA==", + "requires": { + "async": "^1.5.0", + "express-unless": "^0.3.0", + "jsonwebtoken": "^8.1.0", + "lodash.set": "^4.0.0" + } + }, + "express-unless": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/express-unless/-/express-unless-0.3.1.tgz", + "integrity": "sha1-JVfBRudb65A+LSR/m1ugFFJpbiA=" + }, + "express-validator": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-6.12.0.tgz", + "integrity": "sha512-lcQAdVeAO+pBbHD33nIsDsd+QPakLX08tJ82iEsXj6ezyWCfYjE9RY/g9SVq5z4G0NaIkH8039Oe4r0G92DRyA==", + "requires": { + "lodash": "^4.17.21", + "validator": "^13.5.2" + }, + "dependencies": { + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + } + } + }, "external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -1486,6 +1536,11 @@ "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", "dev": true }, + "formidable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", + "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==" + }, "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", @@ -1971,6 +2026,35 @@ "minimist": "^1.2.0" } }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, "jsx-ast-utils": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz", @@ -1986,6 +2070,25 @@ "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.1.tgz", "integrity": "sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA==" }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "keyv": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", @@ -2060,6 +2163,46 @@ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, + "lodash.set": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", + "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=" + }, "log-symbols": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", @@ -3738,6 +3881,11 @@ "spdx-expression-parse": "^3.0.0" } }, + "validator": { + "version": "13.6.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.6.0.tgz", + "integrity": "sha512-gVgKbdbHgtxpRyR8K0O6oFZPhhB5tT1jeEHZR0Znr9Svg03U0+r9DXWMrnRAB+HtCStDQKlaIZm42tVsVjqtjg==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/backend/package.json b/backend/package.json index c97bb20..dd28a9a 100644 --- a/backend/package.json +++ b/backend/package.json @@ -21,14 +21,18 @@ "author": "", "license": "ISC", "dependencies": { - "nodemon": "^2.0.4", "body-parser": "^1.19.0", "chai": "^4.2.0", "chalk": "^4.1.0", "dotenv": "^8.2.0", "express": "^4.17.1", + "express-jwt": "^6.0.0", + "express-validator": "^6.12.0", + "formidable": "^1.2.2", + "jsonwebtoken": "^8.5.1", "mocha": "^8.1.3", "morgan": "^1.10.0", + "nodemon": "^2.0.4", "pg": "^7.18.2", "sha.js": "^2.4.11", "sinon": "^9.0.3" diff --git a/backend/routes/api/auth.js b/backend/routes/api/auth.js new file mode 100644 index 0000000..4c390df --- /dev/null +++ b/backend/routes/api/auth.js @@ -0,0 +1,13 @@ +const express = require('express'); +const router = express.Router(); + +const { runValidation } = require('../../validator'); + +const { userSignInValidation } = require('../../validator/user'); + +const { login } = require('../../controllers/auth'); + +router.post('/login', userSignInValidation, runValidation, login); + + +module.exports = router; \ No newline at end of file diff --git a/backend/routes/api/index.js b/backend/routes/api/index.js new file mode 100644 index 0000000..8394300 --- /dev/null +++ b/backend/routes/api/index.js @@ -0,0 +1,7 @@ +const express = require('express'); +const router = express.Router(); + +router.use('/user', require('./user')); +router.use('/auth', require('./auth')); + +module.exports = router; \ No newline at end of file diff --git a/backend/routes/api/user.js b/backend/routes/api/user.js new file mode 100644 index 0000000..254f3be --- /dev/null +++ b/backend/routes/api/user.js @@ -0,0 +1,11 @@ +const express = require('express'); +const router = express.Router(); + +const { requireSignin } = require('../../controllers/auth'); +const { show, update, picture } = require('../../controllers/user'); + +router.get('/:id', requireSignin, show); +router.put('/:id', requireSignin, update); +router.get('/profile-pic/:id', picture); + +module.exports = router; diff --git a/backend/services/user.js b/backend/services/user.js index 2274a12..0c0d0d4 100644 --- a/backend/services/user.js +++ b/backend/services/user.js @@ -1,3 +1,4 @@ +const { query } = require('express-validator'); const shajs = require('sha.js'); const db = require('../sql/db'); @@ -13,7 +14,7 @@ const hashPassword = (email, password) => shajs('sha256').update(`${email}${pass const authenticateUser = async (email, password) => { const hash = hashPassword(email, password); const queryText = { - text: ` SELECT s.id, s.email, s.first_name as firstName, s.last_name as lastName + text: ` SELECT s.id, s.email, s.first_name as firstName, s.last_name as lastName, s.country, s.city, s.phone_number as phoneNumber, s.email_alert as emailAlert, s.sms_alert as smsAlert FROM users s WHERE email = $1 AND password = $2`, values: [email, hash], @@ -30,6 +31,85 @@ const authenticateUser = async (email, password) => { } }; +const listUsers = async (params = {}) => { + const queryText = { text: `SELECT s.* from users s` }; + try { + const { rows } = await db.query(queryText); + if (rows[0]) { + return rows; + } + throw (new Error('Not Found')); + } catch (error) { + throw (new Error('Not Found')); + } +} + +const getUserById = async (id) => { + const queryText = { + text: ` SELECT s.id, s.email, s.first_name as firstName, s.last_name as lastName, s.country, s.city, s.phone_number as phoneNumber, s.email_alert as emailAlert, s.sms_alert as smsAlert + FROM users s + WHERE id = $1`, + values: [id], + }; + try { + const { rows } = await db.query(queryText); + if (rows[0]) { + return rows[0]; + } + throw (new Error('Not Found')); + } catch (error) { + throw (new Error('Not Found')); + } +} +const updateUser = async (data) => { + let updateFieldArr = []; + let updateFieldData = []; + + let index = 1; + for (let key in data) { + const field = `${key} = $${index}`; + updateFieldArr.push(field); + updateFieldData.push(data[key]); + index++; + } + + const fields = updateFieldArr.join(', '); + + let text = `UPDATE users SET ${fields} WHERE id = $${index}`; + updateFieldData.push(data.id); + let queryText = { text, values: updateFieldData }; + + try { + const resp = await db.query(queryText); + return resp.rowCount; + } catch (error) { + console.log(error.stack); + return 0; + } + +} + +const getUserProfilePicture = async (id) => { + const queryText = { + text: `SELECT s.profile_picture as profilePicture, s.profile_picture_content_type as profilePictureContentType FROM users s WHERE id = $1`, + values: [id] + }; + try { + const { rows } = await db.query(queryText); + if (rows[0]) { + return rows[0]; + } + throw (new Error('Not Found')); + } catch (error) { + throw (new Error('Not Found')); + } +} + module.exports = { authenticateUser, + hashPassword, + listUsers, + getUserById, + updateUser, + getUserProfilePicture }; diff --git a/backend/sql/insert.sql b/backend/sql/insert.sql index 2774d0b..d33c42b 100644 --- a/backend/sql/insert.sql +++ b/backend/sql/insert.sql @@ -1,6 +1,5 @@ - - INSERT INTO users (email,password,first_name) VALUES ('user-1-company@cementys.com','16fdfc9f3214d683bc4dc6a47c50550bb6ebe284cd118020455c2cfbc3d0bf32','user1'), --password:toto ('user-2-companies@cementys.com','f22ebd25bd26ece6c9c30841111a96a2af063531926d8ba511c7f3a0a08a2e63','user2'), --password:pass -('user-0-company@cementys.com','043d989ad0f63a6a3878c8fe5af111278f1a2ecc6e93fdcb090ba0f38942639a','user3'); --password:sql \ No newline at end of file +('user-0-company@cementys.com','043d989ad0f63a6a3878c8fe5af111278f1a2ecc6e93fdcb090ba0f38942639a','user3'), --password:sql +('river@test.com','d3a411f2df43971875bb6da37912956b2d14e6ecb9a0d3189e50ba42bb36237d','testriver'); --password:abc123456 \ No newline at end of file diff --git a/backend/sql/schema.sql b/backend/sql/schema.sql index 4dc14df..fbc0ac1 100644 --- a/backend/sql/schema.sql +++ b/backend/sql/schema.sql @@ -12,6 +12,10 @@ CREATE TABLE users ( city TEXT, phone_number TEXT, position TEXT, + profile_picture bytea, + profile_picture_content_type TEXT, + email_alert smallint DEFAULT 1, + sms_alert smallint DEFAULT 1, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); diff --git a/backend/utils/index.js b/backend/utils/index.js new file mode 100644 index 0000000..3ffd5bf --- /dev/null +++ b/backend/utils/index.js @@ -0,0 +1,7 @@ +const checkEmail = (value) => { + const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + return re.test(String(value).toLowerCase()); +}; +module.exports = { + checkEmail +} \ No newline at end of file diff --git a/backend/validator/index.js b/backend/validator/index.js new file mode 100644 index 0000000..3e62ed3 --- /dev/null +++ b/backend/validator/index.js @@ -0,0 +1,9 @@ +const { validationResult } = require('express-validator') + +exports.runValidation = (req, res, next) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(422).json({ message: errors.array()[0].msg }) + } + next(); +} \ No newline at end of file diff --git a/backend/validator/user.js b/backend/validator/user.js new file mode 100644 index 0000000..d74a0d7 --- /dev/null +++ b/backend/validator/user.js @@ -0,0 +1,10 @@ +const { check } = require('express-validator'); + +exports.userSignInValidation = [ + check('email') + .isEmail() + .withMessage('Email must be valid'), + check('password') + .isLength({ min: 4 }) + .withMessage('Password must be at least 6 characters long') +]; \ No newline at end of file diff --git a/frontend/.eslintcache b/frontend/.eslintcache index 381824e..90c45dd 100644 --- a/frontend/.eslintcache +++ b/frontend/.eslintcache @@ -1 +1 @@ -[{"/home/khaled/github.com/cementysdev/Coding_test_thm/frontend/src/index.jsx":"1","/home/khaled/github.com/cementysdev/Coding_test_thm/frontend/src/App.jsx":"2","/home/khaled/github.com/cementysdev/Coding_test_thm/frontend/src/pages/profile/index.jsx":"3","/home/khaled/github.com/cementysdev/Coding_test_thm/frontend/src/componenets/Profile/index.jsx":"4"},{"size":198,"mtime":1607621387270,"results":"5","hashOfConfig":"6"},{"size":207,"mtime":1607621387270,"results":"7","hashOfConfig":"6"},{"size":210,"mtime":1607621387270,"results":"8","hashOfConfig":"6"},{"size":379,"mtime":1607621387270,"results":"9","hashOfConfig":"6"},{"filePath":"10","messages":"11","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"tvar3u",{"filePath":"12","messages":"13","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"14","messages":"15","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"16","messages":"17","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"/home/khaled/github.com/cementysdev/Coding_test_thm/frontend/src/index.jsx",[],"/home/khaled/github.com/cementysdev/Coding_test_thm/frontend/src/App.jsx",[],"/home/khaled/github.com/cementysdev/Coding_test_thm/frontend/src/pages/profile/index.jsx",[],"/home/khaled/github.com/cementysdev/Coding_test_thm/frontend/src/componenets/Profile/index.jsx",[]] \ No newline at end of file +[{"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\index.jsx":"1","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\App.jsx":"2","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\pages\\profile\\index.jsx":"3","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\Profile\\index.jsx":"4","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\stores\\rootReducer.js":"5","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\stores\\user\\userReducer.js":"6","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\stores\\user\\userAction.js":"7","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\tools\\api.js":"8","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\TextField\\index.jsx":"9","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\FileField\\index.jsx":"10","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\CheckBoxField\\index.jsx":"11"},{"size":231,"mtime":1625822631025,"results":"12","hashOfConfig":"13"},{"size":533,"mtime":1625831714289,"results":"14","hashOfConfig":"13"},{"size":669,"mtime":1625914761557,"results":"15","hashOfConfig":"13"},{"size":4290,"mtime":1625935626539,"results":"16","hashOfConfig":"13"},{"size":240,"mtime":1625930751375,"results":"17","hashOfConfig":"13"},{"size":1949,"mtime":1625930688382,"results":"18","hashOfConfig":"13"},{"size":2575,"mtime":1625930710335,"results":"19","hashOfConfig":"13"},{"size":585,"mtime":1625882906064,"results":"20","hashOfConfig":"13"},{"size":679,"mtime":1625906627345,"results":"21","hashOfConfig":"13"},{"size":1832,"mtime":1625914851749,"results":"22","hashOfConfig":"13"},{"size":1013,"mtime":1625930391060,"results":"23","hashOfConfig":"13"},{"filePath":"24","messages":"25","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"26"},"1wj3y41",{"filePath":"27","messages":"28","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"26"},{"filePath":"29","messages":"30","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"31","messages":"32","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"33","messages":"34","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"35","messages":"36","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"37","messages":"38","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"39","messages":"40","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"26"},{"filePath":"41","messages":"42","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"26"},{"filePath":"43","messages":"44","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"45","messages":"46","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\index.jsx",[],["47","48","49","50","51","52"],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\App.jsx",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\pages\\profile\\index.jsx",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\Profile\\index.jsx",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\stores\\rootReducer.js",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\stores\\user\\userReducer.js",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\stores\\user\\userAction.js",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\tools\\api.js",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\TextField\\index.jsx",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\FileField\\index.jsx",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\CheckBoxField\\index.jsx",[],{"ruleId":"53","replacedBy":"54"},{"ruleId":"55","replacedBy":"56"},{"ruleId":"57","replacedBy":"58"},{"ruleId":"59","replacedBy":"60"},{"ruleId":"61","replacedBy":"62"},{"ruleId":"63","replacedBy":"64"},"lines-around-directive",["65"],"no-spaced-func",["66"],"global-require",[],"no-buffer-constructor",[],"no-new-require",[],"no-path-concat",[],"padding-line-between-statements","func-call-spacing"] \ No newline at end of file diff --git a/frontend/.eslintrc b/frontend/.eslintrc index e799eb6..ad6a042 100644 --- a/frontend/.eslintrc +++ b/frontend/.eslintrc @@ -6,7 +6,9 @@ "max-len": ["warn", 200], "no-underscore-dangle": [1], "no-console": "off", - "react/jsx-props-no-spreading": "off" + "react/jsx-props-no-spreading": "off", + "linebreak-style": 0, + "no-unneeded-ternary": 0 }, "env": { "browser": true diff --git a/frontend/craco.config.js b/frontend/craco.config.js new file mode 100644 index 0000000..dd8eecb --- /dev/null +++ b/frontend/craco.config.js @@ -0,0 +1,10 @@ +module.exports = { + style: { + postcss: { + plugins: [ + require('tailwindcss'), + require('autoprefixer'), + ], + }, + }, +}; \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 3ffc0c7..a84ad13 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1158,6 +1158,63 @@ "minimist": "^1.2.0" } }, + "@craco/craco": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@craco/craco/-/craco-6.2.0.tgz", + "integrity": "sha512-kLc4GSdgR9D5JiZmSxtzbvBKcUFSJqMXImRjjYf5pacwiyAs3XfQwai7T+pExfLQNUnytgkL8jRFUJeYrkVr7g==", + "requires": { + "cross-spawn": "^7.0.0", + "lodash": "^4.17.15", + "semver": "^7.3.2", + "webpack-merge": "^4.2.2" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "requires": { + "isexe": "^2.0.0" + } + } + } + }, "@csstools/convert-colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz", @@ -1205,6 +1262,15 @@ } } }, + "@fullhuman/postcss-purgecss": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@fullhuman/postcss-purgecss/-/postcss-purgecss-3.1.3.tgz", + "integrity": "sha512-kwOXw8fZ0Lt1QmeOOrd+o4Ibvp4UTEBFQbzvWldjlKv5n+G9sXfIPn1hh63IQIL8K8vbvv1oYMJiIUbuy9bGaA==", + "dev": true, + "requires": { + "purgecss": "^3.1.3" + } + }, "@hapi/address": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", @@ -2540,6 +2606,15 @@ "@types/node": "*" } }, + "@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "requires": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "@types/html-minifier-terser": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", @@ -2629,6 +2704,17 @@ "@types/react": "*" } }, + "@types/react-redux": { + "version": "7.1.18", + "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.18.tgz", + "integrity": "sha512-9iwAsPyJ9DLTRH+OFeIrm9cAbIj1i2ANL3sKQFATqnPWRbg+jEFXyZOKHiQK/N86pNRXbb4HRxAxo0SIX1XwzQ==", + "requires": { + "@types/hoist-non-react-statics": "^3.3.0", + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0", + "redux": "^4.0.0" + } + }, "@types/resolve": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", @@ -3095,6 +3181,17 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==" }, + "acorn-node": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", + "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", + "dev": true, + "requires": { + "acorn": "^7.0.0", + "acorn-walk": "^7.0.0", + "xtend": "^4.0.2" + } + }, "acorn-walk": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", @@ -3201,6 +3298,12 @@ "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, + "arg": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.0.tgz", + "integrity": "sha512-4P8Zm2H+BRS+c/xX1LrHw0qKpEhdlZjLCgWy+d78T9vqa2Z2SiD2wMrYuWIAFy5IZUD7nnNXroRttz+0RzlrzQ==", + "dev": true + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -3507,9 +3610,9 @@ "integrity": "sha512-5Kgy8Cz6LPC9DJcNb3yjAXTu3XihQgEdnIg50c//zOC/MyLP0Clg+Y8Sh9ZjjnvBrDZU4DgXS9C3T9r4/scGZQ==" }, "axios": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.20.0.tgz", - "integrity": "sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", "requires": { "follow-redirects": "^1.10.0" } @@ -4023,8 +4126,7 @@ "binary-extensions": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", - "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", - "optional": true + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==" }, "bindings": { "version": "1.5.0", @@ -4379,6 +4481,12 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==" }, + "camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true + }, "caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -5060,6 +5168,12 @@ "source-map": "^0.6.1" } }, + "css-unit-converter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.2.tgz", + "integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==", + "dev": true + }, "css-what": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", @@ -5378,6 +5492,12 @@ } } }, + "defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", + "dev": true + }, "del": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", @@ -5484,6 +5604,23 @@ } } }, + "detective": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz", + "integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==", + "dev": true, + "requires": { + "acorn-node": "^1.6.1", + "defined": "^1.0.0", + "minimist": "^1.1.1" + } + }, + "didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, "diff-sequences": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz", @@ -5514,6 +5651,12 @@ "path-type": "^4.0.0" } }, + "dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, "dns-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", @@ -7373,6 +7516,32 @@ "mime-types": "^2.1.12" } }, + "formik": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/formik/-/formik-2.2.9.tgz", + "integrity": "sha512-LQLcISMmf1r5at4/gyJigGn0gOwFbeEAlji+N9InZF6LIMXnFNkO42sCI8Jt84YZggpD4cPWObAZaxpEFtSzNA==", + "requires": { + "deepmerge": "^2.1.1", + "hoist-non-react-statics": "^3.3.0", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "react-fast-compare": "^2.0.1", + "tiny-warning": "^1.0.2", + "tslib": "^1.10.0" + }, + "dependencies": { + "deepmerge": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", + "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==" + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + } + } + }, "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", @@ -7769,6 +7938,14 @@ "minimalistic-crypto-utils": "^1.0.1" } }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + } + }, "hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -7868,6 +8045,12 @@ } } }, + "html-tags": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz", + "integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==", + "dev": true + }, "html-webpack-plugin": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.5.0.tgz", @@ -8323,7 +8506,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "optional": true, "requires": { "binary-extensions": "^2.0.0" } @@ -11284,6 +11466,12 @@ "type-check": "~0.3.2" } }, + "lilconfig": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.3.tgz", + "integrity": "sha512-EHKqr/+ZvdKCifpNrJCKxBTgk5XupZA3y/aCPY9mxfgBzmgh93Mt/WqjjQ38oMxXuvDokaKiM3lAgvSH2sjtHg==", + "dev": true + }, "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -11343,6 +11531,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, + "lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, "lodash._reinterpolate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", @@ -11375,6 +11568,18 @@ "lodash._reinterpolate": "^3.0.0" } }, + "lodash.toarray": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz", + "integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=", + "dev": true + }, + "lodash.topath": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/lodash.topath/-/lodash.topath-4.5.2.tgz", + "integrity": "sha1-NhY1Hzu6YZlKCTGYlmC9AyVP0Ak=", + "dev": true + }, "lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -11741,6 +11946,12 @@ "minimist": "^1.2.5" } }, + "modern-normalize": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/modern-normalize/-/modern-normalize-1.1.0.tgz", + "integrity": "sha512-2lMlY1Yc1+CUy0gw4H95uNN7vjbpoED7NNRSBHE25nWfLBdmMzFCsPshlzbxHz+gYMcBEUN8V4pU16prcdPSgA==", + "dev": true + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -11851,6 +12062,15 @@ } } }, + "node-emoji": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz", + "integrity": "sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw==", + "dev": true, + "requires": { + "lodash.toarray": "^4.4.0" + } + }, "node-forge": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", @@ -12069,6 +12289,12 @@ } } }, + "object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "dev": true + }, "object-inspect": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", @@ -12704,9 +12930,9 @@ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" }, "postcss": { - "version": "7.0.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", - "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", "requires": { "chalk": "^2.4.2", "source-map": "^0.6.1", @@ -12983,6 +13209,37 @@ "postcss": "^7.0.2" } }, + "postcss-functions": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-functions/-/postcss-functions-3.0.0.tgz", + "integrity": "sha1-DpTQFERwCkgd4g3k1V+yZAVkJQ4=", + "dev": true, + "requires": { + "glob": "^7.1.2", + "object-assign": "^4.1.1", + "postcss": "^6.0.9", + "postcss-value-parser": "^3.3.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, "postcss-gap-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz", @@ -13000,6 +13257,16 @@ "postcss-values-parser": "^2.0.0" } }, + "postcss-import": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.0.2.tgz", + "integrity": "sha512-BJ2pVK4KhUyMcqjuKs9RijV5tatNzNa73e/32aBVE/ejYPe37iH+6vAu9WvqUkB5OAYgLHzbSvzHnorybJCm9g==", + "requires": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + } + }, "postcss-initial": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-3.0.2.tgz", @@ -13009,6 +13276,16 @@ "postcss": "^7.0.2" } }, + "postcss-js": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-2.0.3.tgz", + "integrity": "sha512-zS59pAk3deu6dVHyrGqmC3oDXBdNdajk4k1RyxeVXCrcEDBUBHoIhE4QTsmhxgzXxsaqFDAkUZfmMa5f/N/79w==", + "dev": true, + "requires": { + "camelcase-css": "^2.0.1", + "postcss": "^7.0.18" + } + }, "postcss-lab-function": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz", @@ -13278,14 +13555,21 @@ "postcss": "^7.0.6" } }, - "postcss-nesting": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-7.0.1.tgz", - "integrity": "sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg==", + "postcss-nested": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-4.2.3.tgz", + "integrity": "sha512-rOv0W1HquRCamWy2kFl3QazJMMe1ku6rCFoAAH+9AcxdbpDeBr6k968MLWuLjvjMcGEip01ak09hKOEgpK9hvw==", + "dev": true, "requires": { - "postcss": "^7.0.2" + "postcss": "^7.0.32", + "postcss-selector-parser": "^6.0.2" } }, + "postcss-nesting": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-8.0.1.tgz", + "integrity": "sha512-cHPNhW5VvRQjszFDxmy16mis9qFQqQLBNw6KVmueLqqE3M182ZAk9+QoxGqbGVryzLVhannw2B5Yhosqq522fA==" + }, "postcss-normalize": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/postcss-normalize/-/postcss-normalize-8.0.1.tgz", @@ -13533,6 +13817,16 @@ "postcss-replace-overflow-wrap": "^3.0.0", "postcss-selector-matches": "^4.0.0", "postcss-selector-not": "^4.0.0" + }, + "dependencies": { + "postcss-nesting": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-7.0.1.tgz", + "integrity": "sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg==", + "requires": { + "postcss": "^7.0.2" + } + } } }, "postcss-pseudo-class-any-link": { @@ -13732,6 +14026,12 @@ } } }, + "pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", + "dev": true + }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -13853,6 +14153,49 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, + "purgecss": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/purgecss/-/purgecss-3.1.3.tgz", + "integrity": "sha512-hRSLN9mguJ2lzlIQtW4qmPS2kh6oMnA9RxdIYK8sz18QYqd6ePp4GNDl18oWHA1f2v2NEQIh51CO8s/E3YGckQ==", + "dev": true, + "requires": { + "commander": "^6.0.0", + "glob": "^7.0.0", + "postcss": "^8.2.1", + "postcss-selector-parser": "^6.0.2" + }, + "dependencies": { + "colorette": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", + "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", + "dev": true + }, + "commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true + }, + "nanoid": { + "version": "3.1.23", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz", + "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==", + "dev": true + }, + "postcss": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.5.tgz", + "integrity": "sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA==", + "dev": true, + "requires": { + "colorette": "^1.2.2", + "nanoid": "^3.1.23", + "source-map-js": "^0.6.2" + } + } + } + }, "q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", @@ -13887,6 +14230,12 @@ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true + }, "raf": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", @@ -14074,11 +14423,39 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.8.tgz", "integrity": "sha512-HvPuUQnLp5H7TouGq3kzBeioJmXms1wHy9EGjz2OURWBp4qZO6AfGEcnxts1D/CbwPLRAgTMPCEgYhA3sEM4vw==" }, + "react-fast-compare": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", + "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" + }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "react-redux": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.4.tgz", + "integrity": "sha512-hOQ5eOSkEJEXdpIKbnRyl04LhaWabkDPV+Ix97wqQX3T3d2NQ8DUblNXXtNMavc7DpswyQM6xfaN4HQDKNY2JA==", + "requires": { + "@babel/runtime": "^7.12.1", + "@types/react-redux": "^7.1.16", + "hoist-non-react-statics": "^3.3.2", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", + "react-is": "^16.13.1" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz", + "integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + } + } + }, "react-refresh": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz", @@ -14504,6 +14881,21 @@ } } }, + "read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha1-5mTvMRYRZsl1HNvo28+GtftY93Q=", + "requires": { + "pify": "^2.3.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + } + } + }, "read-pkg": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", @@ -14614,6 +15006,47 @@ "strip-indent": "^3.0.0" } }, + "reduce-css-calc": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz", + "integrity": "sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==", + "dev": true, + "requires": { + "css-unit-converter": "^1.1.1", + "postcss-value-parser": "^3.3.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "redux": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.1.0.tgz", + "integrity": "sha512-uI2dQN43zqLWCt6B/BMGRMY6db7TTY4qeHHfGeKb3EOhmOKjU3KdWvNLJyqaHRksv/ErdNH7cFZWg9jXtewy4g==", + "requires": { + "@babel/runtime": "^7.9.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz", + "integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + } + } + }, + "redux-thunk": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz", + "integrity": "sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==" + }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -15792,6 +16225,12 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, + "source-map-js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz", + "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==", + "dev": true + }, "source-map-resolve": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", @@ -16400,6 +16839,281 @@ } } }, + "tailwindcss": { + "version": "npm:@tailwindcss/postcss7-compat@2.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss7-compat/-/postcss7-compat-2.2.4.tgz", + "integrity": "sha512-lFIBdD1D2w3RgHFg7kNB7U5LOlfbd+KXTzcLyC/RlQ9eVko6GjNCKpN/kdmfF9wiGxbSDT/3mousXeMZdOOuBg==", + "dev": true, + "requires": { + "@fullhuman/postcss-purgecss": "^3.1.3", + "arg": "^5.0.0", + "autoprefixer": "^9", + "bytes": "^3.0.0", + "chalk": "^4.1.1", + "chokidar": "^3.5.2", + "color": "^3.1.3", + "cosmiconfig": "^7.0.0", + "detective": "^5.2.0", + "didyoumean": "^1.2.1", + "dlv": "^1.1.3", + "fast-glob": "^3.2.5", + "fs-extra": "^10.0.0", + "glob-parent": "^6.0.0", + "html-tags": "^3.1.0", + "is-glob": "^4.0.1", + "lodash": "^4.17.21", + "lodash.topath": "^4.5.2", + "modern-normalize": "^1.1.0", + "node-emoji": "^1.8.1", + "normalize-path": "^3.0.0", + "object-hash": "^2.2.0", + "postcss": "^7", + "postcss-functions": "^3", + "postcss-js": "^2", + "postcss-load-config": "^3.1.0", + "postcss-nested": "^4", + "postcss-selector-parser": "^6.0.6", + "postcss-value-parser": "^4.1.0", + "pretty-hrtime": "^1.0.3", + "quick-lru": "^5.1.1", + "reduce-css-calc": "^2.1.8", + "resolve": "^1.20.0", + "tmp": "^0.2.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chokidar": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "fast-glob": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", + "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "fs-extra": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", + "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "glob-parent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.0.tgz", + "integrity": "sha512-Hdd4287VEJcZXUwv1l8a+vXC1GjOQqXe+VS30w/ypihpcnu9M1n3xeYeJu5CBpeEQj2nAab2xxz28GuA3vp4Ww==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "import-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-3.0.0.tgz", + "integrity": "sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==", + "dev": true, + "requires": { + "import-from": "^3.0.0" + } + }, + "import-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz", + "integrity": "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + }, + "dependencies": { + "picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true + } + } + }, + "postcss-load-config": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.0.tgz", + "integrity": "sha512-ipM8Ds01ZUophjDTQYSVP70slFSYg3T0/zyfII5vzhN6V57YSxMgG5syXuwi5VtS8wSf3iL30v0uBdoIVx4Q0g==", + "dev": true, + "requires": { + "import-cwd": "^3.0.0", + "lilconfig": "^2.0.3", + "yaml": "^1.10.2" + } + }, + "postcss-selector-parser": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz", + "integrity": "sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + }, + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true + } + } + }, "tapable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", @@ -16616,6 +17330,31 @@ "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" }, + "tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + }, + "dependencies": { + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, "tmpl": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", @@ -18183,6 +18922,14 @@ } } }, + "webpack-merge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz", + "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==", + "requires": { + "lodash": "^4.17.15" + } + }, "webpack-sources": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index 4393fa9..fc7019d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -3,30 +3,41 @@ "version": "0.1.0", "private": true, "dependencies": { + "@craco/craco": "^6.2.0", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", - "axios": "^0.20.1", + "axios": "^0.21.1", + "formik": "^2.2.9", + "postcss-import": "^14.0.2", + "postcss-nesting": "^8.0.1", "prop-types": "^15.7.2", "react": "^16.14.0", "react-dom": "^16.14.0", - "react-scripts": "^4.0.1" + "react-redux": "^7.2.4", + "react-scripts": "^4.0.1", + "redux": "^4.1.0", + "redux-thunk": "^2.3.0" }, "devDependencies": { + "autoprefixer": "^9.8.6", "eslint": "^7.11.0", "eslint-config-airbnb": "^18.2.1", "eslint-plugin-import": "^2.22.1", "eslint-plugin-jsx-a11y": "^6.4.1", "eslint-plugin-react": "^7.21.5", - "eslint-plugin-react-hooks": "^2.5.0" + "eslint-plugin-react-hooks": "^2.5.0", + "postcss": "^7.0.36", + "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.4" }, "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test", + "start": "craco start", + "build": "craco build", + "test": "craco test", "eject": "react-scripts eject", "lint": "./node_modules/.bin/eslint --ignore-pattern '**/node_modules/*' --ignore-pattern '**/*.test.js' --fix ./src --ext .js --ext .jsx" }, + "proxy": "http://localhost:3002", "eslintConfig": { "extends": "react-app" }, diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index df441e7..1f2a64d 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,13 +1,26 @@ import React from 'react'; + import './App.css'; + +import { createStore, applyMiddleware } from 'redux'; +import { Provider } from 'react-redux'; +import thunk from 'redux-thunk'; +import rootReducer from './stores/rootReducer'; + import ProfilePage from './pages/profile'; +const store = createStore( + rootReducer, + applyMiddleware(thunk), +); + function App() { return ( -
- -
+ +
+ +
+
); } - export default App; diff --git a/frontend/src/componenets/CheckBoxField/CheckBoxField.css b/frontend/src/componenets/CheckBoxField/CheckBoxField.css new file mode 100644 index 0000000..592cb38 --- /dev/null +++ b/frontend/src/componenets/CheckBoxField/CheckBoxField.css @@ -0,0 +1,11 @@ +.check-box { + @apply w-[1.25rem] h-[1.25rem] border-2 border-[#153376] rounded relative; +} +.check-box.active::before { + @apply absolute top-0 left-[4px] right-0 bottom-0 w-[8px] h-[13px] border-[#153376]; + content: ''; + border-width: 0 3px 3px 0; + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg); +} \ No newline at end of file diff --git a/frontend/src/componenets/CheckBoxField/index.jsx b/frontend/src/componenets/CheckBoxField/index.jsx new file mode 100644 index 0000000..73ce12f --- /dev/null +++ b/frontend/src/componenets/CheckBoxField/index.jsx @@ -0,0 +1,39 @@ +import React, { useState, useEffect } from 'react'; +import PropTypes from 'prop-types'; + +import './CheckBoxField.css'; + +const CheckBoxField = (props) => { + const { field, form, label } = props; + const { name, value } = field; + const [checked, setChecked] = useState(); + + const handleCheckBoxChange = () => { + const checkVal = checked === 1 ? 0 : 1; + form.setFieldValue(name, checkVal); + setChecked(checkVal); + }; + + useEffect(() => { + setChecked(value); + }, [value]); + + return ( +
+
+ ); +}; + +CheckBoxField.propTypes = { + field: PropTypes.objectOf(PropTypes.any).isRequired, + form: PropTypes.objectOf(PropTypes.any).isRequired, + label: PropTypes.string, +}; + +CheckBoxField.defaultProps = { + label: '', +}; + +export default CheckBoxField; diff --git a/frontend/src/componenets/FileField/index.jsx b/frontend/src/componenets/FileField/index.jsx new file mode 100644 index 0000000..231d248 --- /dev/null +++ b/frontend/src/componenets/FileField/index.jsx @@ -0,0 +1,57 @@ +import React, { + useState, useRef, useEffect, +} from 'react'; +import { useSelector } from 'react-redux'; +import PropTypes from 'prop-types'; + +const FileField = (props) => { + const { field, form, type } = props; + const { name } = field; + const fileRef = useRef(null); + const [avatar, setAvatar] = useState(''); + const onAvatarClick = () => { + fileRef.current.click(); + }; + const handleOnFileChange = (event) => { + const fileAvatar = event.target.files[0]; + form.setFieldValue('profile', fileAvatar); + const reader = new FileReader(); + reader.addEventListener('load', () => { + setAvatar(reader.result); + }); + if (fileAvatar) reader.readAsDataURL(fileAvatar); + }; + const { user } = useSelector((state) => state.userLoginReducer); + useEffect(() => { + if (user.id) setAvatar(`${process.env.REACT_APP_API_URL}/api/user/profile-pic/${user.id}`); + }, [user]); + return ( +
+ handleOnFileChange(event)} + /> +
+ + +
+
+ ); +}; + +FileField.propTypes = { + field: PropTypes.objectOf(PropTypes.any).isRequired, + form: PropTypes.objectOf(PropTypes.any).isRequired, + type: PropTypes.string, +}; + +FileField.defaultProps = { + type: 'file', +}; + +export default FileField; diff --git a/frontend/src/componenets/Profile/index.jsx b/frontend/src/componenets/Profile/index.jsx index 214cb85..df64a10 100644 --- a/frontend/src/componenets/Profile/index.jsx +++ b/frontend/src/componenets/Profile/index.jsx @@ -1,15 +1,106 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; +import { useDispatch, useSelector } from 'react-redux'; + +import { + Formik, Form, FastField, +} from 'formik'; +import TextField from '../TextField'; +import FileField from '../FileField'; +import CheckBoxField from '../CheckBoxField'; + +import { userUpdateProfile, userUpdateProfileReset } from '../../stores/user/userAction'; + function Profile({ user }) { + const dispatch = useDispatch(); + + const { loading, updated, error } = useSelector((state) => state.userUpdateReducer); + const { userInfo } = useSelector((state) => state.userByIdReducer); + + const getUserValue = (values) => ({ + firstname: values.firstname ? values.firstname : '', + lastname: values.lastname ? values.lastname : '', + email: values.email ? values.email : '', + country: values.country ? values.country : '', + city: values.city ? values.city : '', + phonenumber: values.phonenumber ? values.phonenumber : '', + emailalert: values.emailalert, + smsalert: values.smsalert, + profile: '', + }); + + const [initialValues, setInitialValues] = useState({ ...getUserValue({}), profile: '' }); + + const handleUpdateUser = (values) => { + const formData = new FormData(); + formData.append('firstname', values.firstname); + formData.append('lastname', values.lastname); + formData.append('email', values.email); + formData.append('country', values.country); + formData.append('city', values.city); + formData.append('phonenumber', values.phonenumber); + formData.append('emailalert', values.emailalert ? 1 : 0); + formData.append('smsalert', values.smsalert ? 1 : 0); + formData.append('profile', values.profile); + + dispatch(userUpdateProfile(formData, user.id)); + }; + useEffect(() => { + if (user.id) { + const userValue = getUserValue(user); + setInitialValues({ ...userValue, profile: '' }); + } + }, [user]); + + useEffect(() => { + if (userInfo.id) { + const userUpdate = getUserValue(userInfo); + setInitialValues((prev) => ({ ...prev, ...userUpdate })); + } + }, [userInfo]); + + useEffect(() => { + if (updated || error) setTimeout(() => { dispatch(userUpdateProfileReset()); }, 3000); + }, [updated, error]); + return ( -
-

Profile Component

-

- email: - {user.email} -

-
+ handleUpdateUser(values)} + > +
+ {loading ?

Processing...

: ''} + {updated ?

Update Profile Success

: ''} + {error ?

{error}

: '' } +
+ +
+ + + + + + + + + {/*
+ + Email Alert +
+
+ + Sms Alert +
*/} +
+ + +
+
); } diff --git a/frontend/src/componenets/TextField/index.jsx b/frontend/src/componenets/TextField/index.jsx new file mode 100644 index 0000000..8da434d --- /dev/null +++ b/frontend/src/componenets/TextField/index.jsx @@ -0,0 +1,25 @@ +import React, { useState } from 'react'; + +import { ErrorMessage, useField } from 'formik'; + +const TextField = (props) => { + const [field] = useField(props); + const [focus, setFocus] = useState(false); + return ( +
+ { + setFocus(false); + }} + onFocus={() => setFocus(true)} + /> + +
+ ); +}; + +export default TextField; diff --git a/frontend/src/index.css b/frontend/src/index.css new file mode 100644 index 0000000..e5c9d51 --- /dev/null +++ b/frontend/src/index.css @@ -0,0 +1,8 @@ +@import url('https://fonts.googleapis.com/css2?family=Source+Sans+Pro:ital@0;1&display=swap'); +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + @apply font-sans; +} \ No newline at end of file diff --git a/frontend/src/index.jsx b/frontend/src/index.jsx index e978020..f452ad2 100644 --- a/frontend/src/index.jsx +++ b/frontend/src/index.jsx @@ -1,5 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; +import './index.css'; import App from './App'; ReactDOM.render( diff --git a/frontend/src/pages/profile/index.jsx b/frontend/src/pages/profile/index.jsx index 3384a70..19adaee 100644 --- a/frontend/src/pages/profile/index.jsx +++ b/frontend/src/pages/profile/index.jsx @@ -1,10 +1,22 @@ -import React from 'react'; +import React, { useEffect } from 'react'; + +import { useSelector, useDispatch } from 'react-redux'; + +import { getUserInfo } from '../../stores/user/userAction'; + import Profile from '../../componenets/Profile'; function ProfilePage() { + const dispatch = useDispatch(); + const { user } = useSelector((state) => state.userLoginReducer); + useEffect(() => { + if (!user.id) { + dispatch(getUserInfo({ email: 'river@test.com', password: 'abc123456' })); + } + }, [dispatch]); return ( -
- +
+
); } diff --git a/frontend/src/stores/rootReducer.js b/frontend/src/stores/rootReducer.js new file mode 100644 index 0000000..cac9303 --- /dev/null +++ b/frontend/src/stores/rootReducer.js @@ -0,0 +1,9 @@ +import { combineReducers } from 'redux'; + +import { userLoginReducer, userUpdateReducer, userByIdReducer } from './user/userReducer'; + +export default combineReducers({ + userLoginReducer, + userUpdateReducer, + userByIdReducer, +}); diff --git a/frontend/src/stores/user/userAction.js b/frontend/src/stores/user/userAction.js new file mode 100644 index 0000000..88ce932 --- /dev/null +++ b/frontend/src/stores/user/userAction.js @@ -0,0 +1,76 @@ +import axios from '../../tools/api'; + +export const USER_LOGIN_REQUEST = 'USER_LOGIN_REQUEST'; +export const USER_LOGIN_SUCCESS = 'USER_LOGIN_SUCCESS'; +export const USER_LOGIN_FAILURE = 'USER_LOGIN_FAILURE'; + +export const USER_UPDATE_PROFILE_REQUEST = 'USER_UPDATE_PROFILE_REQUEST'; +export const USER_UPDATE_PROFILE_SUCCESS = 'USER_UPDATE_PROFILE_SUCCESS'; +export const USER_UPDATE_PROFILE_FAILURE = 'USER_UPDATE_PROFILE_FAILURE'; +export const USER_UPDATE_PROFILE_RESET = 'USER_UPDATE_PROFILE_RESET'; + +export const GET_USER_BY_ID_REQUEST = 'GET_USER_BY_ID_REQUEST'; +export const GET_USER_BY_ID_SUCCESS = 'GET_USER_BY_ID_SUCCESS'; +export const GET_USER_BY_ID_FAILURE = 'GET_USER_BY_ID_FAILURE'; + +export const userLoginRequest = () => ({ + type: USER_LOGIN_REQUEST, +}); + +export const userLoginSuccess = (user) => ({ + type: USER_LOGIN_SUCCESS, + payload: user, +}); + +export const userLoginFailure = (error) => ({ + type: USER_LOGIN_FAILURE, + payload: error, +}); + +export const userUpdateProfileReset = () => ({ + type: USER_UPDATE_PROFILE_RESET, +}); + +export function getUserInfo({ email, password }) { + return async (dispatch) => { + dispatch(userLoginRequest()); + + try { + const resp = await axios.post('/api/auth/login', { email, password }); + dispatch(userLoginSuccess(resp.data)); + localStorage.setItem('token', resp.data.token); + } catch (error) { + const message = error.response && error.response.data ? error.response.data.message : error.message; + dispatch(userLoginFailure(message)); + } + }; +} + +export function getUserById(id) { + return async (dispatch) => { + dispatch({ type: GET_USER_BY_ID_REQUEST }); + + try { + const resp = await axios.get(`/api/user/${id}`); + dispatch({ type: GET_USER_BY_ID_SUCCESS, payload: resp.data }); + } catch (error) { + const message = error.response && error.response.data ? error.response.data.message : error.message; + dispatch({ type: GET_USER_BY_ID_FAILURE, payload: message }); + } + }; +} + +export function userUpdateProfile(data, id) { + return async (dispatch) => { + dispatch({ type: USER_UPDATE_PROFILE_REQUEST }); + + try { + await axios.put(`/api/user/${id}`, data); + dispatch({ type: USER_UPDATE_PROFILE_SUCCESS, payload: 1 }); + dispatch(getUserById(id)); + } catch (error) { + const message = error.response && error.response.data ? error.response.data.message : error.message; + dispatch({ type: USER_UPDATE_PROFILE_FAILURE, payload: message }); + } + }; +} diff --git a/frontend/src/stores/user/userReducer.js b/frontend/src/stores/user/userReducer.js new file mode 100644 index 0000000..8c860a5 --- /dev/null +++ b/frontend/src/stores/user/userReducer.js @@ -0,0 +1,80 @@ +import * as userAction from './userAction'; + +export const userLoginReducer = (state = { user: {}, token: localStorage.getItem('token') ? localStorage.getItem('token') : '' }, action) => { + switch (action.type) { + case userAction.USER_LOGIN_REQUEST: + return { + ...state, + loading: true, + }; + case userAction.USER_LOGIN_SUCCESS: + return { + ...state, + user: action.payload, + loading: false, + }; + case userAction.USER_LOGIN_FAILURE: + return { + ...state, + error: action.payload, + loading: false, + }; + default: + return state; + } +}; + +export const userUpdateReducer = (state = { updated: false }, action) => { + switch (action.type) { + case userAction.USER_UPDATE_PROFILE_REQUEST: + return { + ...state, + loading: true, + }; + case userAction.USER_UPDATE_PROFILE_SUCCESS: + return { + ...state, + updated: action.payload, + loading: false, + }; + case userAction.USER_UPDATE_PROFILE_FAILURE: + return { + ...state, + error: action.payload, + loading: false, + }; + case userAction.USER_UPDATE_PROFILE_RESET: + return { + ...state, + loading: false, + updated: false, + error: null, + }; + default: + return state; + } +}; + +export const userByIdReducer = (state = { userInfo: {} }, action) => { + switch (action.type) { + case userAction.GET_USER_BY_ID_REQUEST: + return { + ...state, + loading: true, + }; + case userAction.GET_USER_BY_ID_SUCCESS: + return { + ...state, + userInfo: action.payload, + loading: false, + }; + case userAction.GET_USER_BY_ID_FAILURE: + return { + ...state, + error: action.payload, + loading: false, + }; + default: + return state; + } +}; diff --git a/frontend/src/tools/api.js b/frontend/src/tools/api.js index 105140c..b808a23 100644 --- a/frontend/src/tools/api.js +++ b/frontend/src/tools/api.js @@ -1,12 +1,15 @@ import axios from 'axios'; -axios.defaults.baseURL = process.env.REACT_APP_API_URL; +// axios.defaults.baseURL = process.env.REACT_APP_API_URL; +axios.defaults.baseURL = '/'; axios.interceptors.request.use( (request) => { + request.headers['Content-Type'] = 'application/json'; + request.headers.Accept = 'application/json'; if (!request.headers.Authorization) { - const token = localStorage ? localStorage.getItem('token') : null; + const token = localStorage ? localStorage.getItem('token') : ''; if (token) { - request.headers.Authorization = token; + request.headers.Authorization = `Bearer ${token}`; } } return request; diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js new file mode 100644 index 0000000..174cbd4 --- /dev/null +++ b/frontend/tailwind.config.js @@ -0,0 +1,16 @@ +module.exports = { + mode: 'jit', + purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'], + darkMode: false, // or 'media' or 'class' + theme: { + extend: { + fontFamily: { + 'sans': ['Source Sans Pro'] + } + } + }, + variants: { + extend: {}, + }, + plugins: [], +} From c7a15abb00dc16f86afd185a41e73528d79d244e Mon Sep 17 00:00:00 2001 From: River Golden Date: Mon, 12 Jul 2021 09:56:50 +0700 Subject: [PATCH 2/3] add api --- frontend/.eslintcache | 2 +- frontend/craco.config.js | 1 + frontend/src/App.css | 4 ---- frontend/src/api/index.js | 11 +++++++++++ frontend/src/componenets/Profile/index.jsx | 17 +++-------------- frontend/src/stores/user/userAction.js | 8 ++++---- 6 files changed, 20 insertions(+), 23 deletions(-) create mode 100644 frontend/src/api/index.js diff --git a/frontend/.eslintcache b/frontend/.eslintcache index 90c45dd..24e825a 100644 --- a/frontend/.eslintcache +++ b/frontend/.eslintcache @@ -1 +1 @@ -[{"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\index.jsx":"1","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\App.jsx":"2","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\pages\\profile\\index.jsx":"3","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\Profile\\index.jsx":"4","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\stores\\rootReducer.js":"5","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\stores\\user\\userReducer.js":"6","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\stores\\user\\userAction.js":"7","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\tools\\api.js":"8","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\TextField\\index.jsx":"9","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\FileField\\index.jsx":"10","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\CheckBoxField\\index.jsx":"11"},{"size":231,"mtime":1625822631025,"results":"12","hashOfConfig":"13"},{"size":533,"mtime":1625831714289,"results":"14","hashOfConfig":"13"},{"size":669,"mtime":1625914761557,"results":"15","hashOfConfig":"13"},{"size":4290,"mtime":1625935626539,"results":"16","hashOfConfig":"13"},{"size":240,"mtime":1625930751375,"results":"17","hashOfConfig":"13"},{"size":1949,"mtime":1625930688382,"results":"18","hashOfConfig":"13"},{"size":2575,"mtime":1625930710335,"results":"19","hashOfConfig":"13"},{"size":585,"mtime":1625882906064,"results":"20","hashOfConfig":"13"},{"size":679,"mtime":1625906627345,"results":"21","hashOfConfig":"13"},{"size":1832,"mtime":1625914851749,"results":"22","hashOfConfig":"13"},{"size":1013,"mtime":1625930391060,"results":"23","hashOfConfig":"13"},{"filePath":"24","messages":"25","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"26"},"1wj3y41",{"filePath":"27","messages":"28","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"26"},{"filePath":"29","messages":"30","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"31","messages":"32","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"33","messages":"34","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"35","messages":"36","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"37","messages":"38","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"39","messages":"40","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"26"},{"filePath":"41","messages":"42","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"26"},{"filePath":"43","messages":"44","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"45","messages":"46","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\index.jsx",[],["47","48","49","50","51","52"],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\App.jsx",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\pages\\profile\\index.jsx",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\Profile\\index.jsx",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\stores\\rootReducer.js",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\stores\\user\\userReducer.js",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\stores\\user\\userAction.js",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\tools\\api.js",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\TextField\\index.jsx",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\FileField\\index.jsx",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\CheckBoxField\\index.jsx",[],{"ruleId":"53","replacedBy":"54"},{"ruleId":"55","replacedBy":"56"},{"ruleId":"57","replacedBy":"58"},{"ruleId":"59","replacedBy":"60"},{"ruleId":"61","replacedBy":"62"},{"ruleId":"63","replacedBy":"64"},"lines-around-directive",["65"],"no-spaced-func",["66"],"global-require",[],"no-buffer-constructor",[],"no-new-require",[],"no-path-concat",[],"padding-line-between-statements","func-call-spacing"] \ No newline at end of file +[{"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\index.jsx":"1","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\App.jsx":"2","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\pages\\profile\\index.jsx":"3","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\Profile\\index.jsx":"4","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\stores\\rootReducer.js":"5","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\stores\\user\\userReducer.js":"6","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\stores\\user\\userAction.js":"7","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\tools\\api.js":"8","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\TextField\\index.jsx":"9","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\FileField\\index.jsx":"10","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\CheckBoxField\\index.jsx":"11","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\api\\index.js":"12"},{"size":231,"mtime":1625822631025,"results":"13","hashOfConfig":"14"},{"size":533,"mtime":1625831714289,"results":"15","hashOfConfig":"14"},{"size":669,"mtime":1625914761557,"results":"16","hashOfConfig":"14"},{"size":3877,"mtime":1626058498870,"results":"17","hashOfConfig":"14"},{"size":240,"mtime":1625930751375,"results":"18","hashOfConfig":"14"},{"size":1949,"mtime":1625930688382,"results":"19","hashOfConfig":"14"},{"size":2546,"mtime":1626058418570,"results":"20","hashOfConfig":"14"},{"size":585,"mtime":1625882906064,"results":"21","hashOfConfig":"14"},{"size":679,"mtime":1625906627345,"results":"22","hashOfConfig":"14"},{"size":1832,"mtime":1625914851749,"results":"23","hashOfConfig":"14"},{"size":1013,"mtime":1625930391060,"results":"24","hashOfConfig":"14"},{"size":371,"mtime":1626058354639,"results":"25","hashOfConfig":"14"},{"filePath":"26","messages":"27","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},"1wj3y41",{"filePath":"29","messages":"30","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"31","messages":"32","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"33","messages":"34","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"35","messages":"36","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"37","messages":"38","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"39","messages":"40","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"41","messages":"42","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"43","messages":"44","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"45","messages":"46","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"47","messages":"48","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"49","messages":"50","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\index.jsx",[],["51","52","53","54","55","56"],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\App.jsx",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\pages\\profile\\index.jsx",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\Profile\\index.jsx",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\stores\\rootReducer.js",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\stores\\user\\userReducer.js",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\stores\\user\\userAction.js",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\tools\\api.js",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\TextField\\index.jsx",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\FileField\\index.jsx",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\CheckBoxField\\index.jsx",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\api\\index.js",[],{"ruleId":"57","replacedBy":"58"},{"ruleId":"59","replacedBy":"60"},{"ruleId":"61","replacedBy":"62"},{"ruleId":"63","replacedBy":"64"},{"ruleId":"65","replacedBy":"66"},{"ruleId":"67","replacedBy":"68"},"lines-around-directive",["69"],"no-spaced-func",["70"],"global-require",[],"no-buffer-constructor",[],"no-new-require",[],"no-path-concat",[],"padding-line-between-statements","func-call-spacing"] \ No newline at end of file diff --git a/frontend/craco.config.js b/frontend/craco.config.js index dd8eecb..0122db0 100644 --- a/frontend/craco.config.js +++ b/frontend/craco.config.js @@ -2,6 +2,7 @@ module.exports = { style: { postcss: { plugins: [ + require('tailwindcss/nesting'), require('tailwindcss'), require('autoprefixer'), ], diff --git a/frontend/src/App.css b/frontend/src/App.css index d984c9f..8b13789 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -1,5 +1 @@ -.container { - display: flex; - justify-content: center; -} diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js new file mode 100644 index 0000000..cd8b4e0 --- /dev/null +++ b/frontend/src/api/index.js @@ -0,0 +1,11 @@ +import axios from '../tools/api'; + +const API = '/api'; +const AUTH = `${API}/auth`; +const USER = `${API}/user`; + +export const getUserInfo = ({ email, password }) => axios.post(`${AUTH}/login`, { email, password }); + +export const getUserById = (id) => axios.get(`${USER}/${id}`); + +export const userUpdateProfile = (data, id) => axios.put(`${USER}/${id}`, data); diff --git a/frontend/src/componenets/Profile/index.jsx b/frontend/src/componenets/Profile/index.jsx index df64a10..57d5182 100644 --- a/frontend/src/componenets/Profile/index.jsx +++ b/frontend/src/componenets/Profile/index.jsx @@ -40,8 +40,8 @@ function Profile({ user }) { formData.append('country', values.country); formData.append('city', values.city); formData.append('phonenumber', values.phonenumber); - formData.append('emailalert', values.emailalert ? 1 : 0); - formData.append('smsalert', values.smsalert ? 1 : 0); + formData.append('emailalert', values.emailalert); + formData.append('smsalert', values.smsalert); formData.append('profile', values.profile); dispatch(userUpdateProfile(formData, user.id)); @@ -75,10 +75,7 @@ function Profile({ user }) { {updated ?

Update Profile Success

: ''} {error ?

{error}

: '' }
- +
@@ -88,14 +85,6 @@ function Profile({ user }) { - {/*
- - Email Alert -
-
- - Sms Alert -
*/}
diff --git a/frontend/src/stores/user/userAction.js b/frontend/src/stores/user/userAction.js index 88ce932..9ac64ca 100644 --- a/frontend/src/stores/user/userAction.js +++ b/frontend/src/stores/user/userAction.js @@ -1,4 +1,4 @@ -import axios from '../../tools/api'; +import * as api from '../../api'; export const USER_LOGIN_REQUEST = 'USER_LOGIN_REQUEST'; export const USER_LOGIN_SUCCESS = 'USER_LOGIN_SUCCESS'; @@ -36,7 +36,7 @@ export function getUserInfo({ email, password }) { dispatch(userLoginRequest()); try { - const resp = await axios.post('/api/auth/login', { email, password }); + const resp = await api.getUserInfo({ email, password }); dispatch(userLoginSuccess(resp.data)); localStorage.setItem('token', resp.data.token); } catch (error) { @@ -51,7 +51,7 @@ export function getUserById(id) { dispatch({ type: GET_USER_BY_ID_REQUEST }); try { - const resp = await axios.get(`/api/user/${id}`); + const resp = await api.getUserById(id); dispatch({ type: GET_USER_BY_ID_SUCCESS, payload: resp.data }); } catch (error) { const message = error.response && error.response.data ? error.response.data.message : error.message; @@ -65,7 +65,7 @@ export function userUpdateProfile(data, id) { dispatch({ type: USER_UPDATE_PROFILE_REQUEST }); try { - await axios.put(`/api/user/${id}`, data); + await api.userUpdateProfile(data, id); dispatch({ type: USER_UPDATE_PROFILE_SUCCESS, payload: 1 }); dispatch(getUserById(id)); } catch (error) { From e1e383354dd2921ae129179cf21ad6e0a7997588 Mon Sep 17 00:00:00 2001 From: River Golden Date: Mon, 12 Jul 2021 20:48:21 +0700 Subject: [PATCH 3/3] test push --- frontend/.eslintcache | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/.eslintcache b/frontend/.eslintcache index 24e825a..b423b8b 100644 --- a/frontend/.eslintcache +++ b/frontend/.eslintcache @@ -1 +1 @@ -[{"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\index.jsx":"1","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\App.jsx":"2","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\pages\\profile\\index.jsx":"3","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\Profile\\index.jsx":"4","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\stores\\rootReducer.js":"5","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\stores\\user\\userReducer.js":"6","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\stores\\user\\userAction.js":"7","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\tools\\api.js":"8","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\TextField\\index.jsx":"9","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\FileField\\index.jsx":"10","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\CheckBoxField\\index.jsx":"11","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\api\\index.js":"12"},{"size":231,"mtime":1625822631025,"results":"13","hashOfConfig":"14"},{"size":533,"mtime":1625831714289,"results":"15","hashOfConfig":"14"},{"size":669,"mtime":1625914761557,"results":"16","hashOfConfig":"14"},{"size":3877,"mtime":1626058498870,"results":"17","hashOfConfig":"14"},{"size":240,"mtime":1625930751375,"results":"18","hashOfConfig":"14"},{"size":1949,"mtime":1625930688382,"results":"19","hashOfConfig":"14"},{"size":2546,"mtime":1626058418570,"results":"20","hashOfConfig":"14"},{"size":585,"mtime":1625882906064,"results":"21","hashOfConfig":"14"},{"size":679,"mtime":1625906627345,"results":"22","hashOfConfig":"14"},{"size":1832,"mtime":1625914851749,"results":"23","hashOfConfig":"14"},{"size":1013,"mtime":1625930391060,"results":"24","hashOfConfig":"14"},{"size":371,"mtime":1626058354639,"results":"25","hashOfConfig":"14"},{"filePath":"26","messages":"27","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},"1wj3y41",{"filePath":"29","messages":"30","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"31","messages":"32","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"33","messages":"34","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"35","messages":"36","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"37","messages":"38","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"39","messages":"40","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"41","messages":"42","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"43","messages":"44","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"45","messages":"46","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"47","messages":"48","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"49","messages":"50","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\index.jsx",[],["51","52","53","54","55","56"],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\App.jsx",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\pages\\profile\\index.jsx",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\Profile\\index.jsx",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\stores\\rootReducer.js",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\stores\\user\\userReducer.js",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\stores\\user\\userAction.js",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\tools\\api.js",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\TextField\\index.jsx",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\FileField\\index.jsx",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\CheckBoxField\\index.jsx",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\api\\index.js",[],{"ruleId":"57","replacedBy":"58"},{"ruleId":"59","replacedBy":"60"},{"ruleId":"61","replacedBy":"62"},{"ruleId":"63","replacedBy":"64"},{"ruleId":"65","replacedBy":"66"},{"ruleId":"67","replacedBy":"68"},"lines-around-directive",["69"],"no-spaced-func",["70"],"global-require",[],"no-buffer-constructor",[],"no-new-require",[],"no-path-concat",[],"padding-line-between-statements","func-call-spacing"] \ No newline at end of file +[{"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\index.jsx":"1","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\App.jsx":"2","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\pages\\profile\\index.jsx":"3","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\Profile\\index.jsx":"4","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\stores\\rootReducer.js":"5","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\stores\\user\\userReducer.js":"6","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\stores\\user\\userAction.js":"7","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\tools\\api.js":"8","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\TextField\\index.jsx":"9","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\FileField\\index.jsx":"10","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\CheckBoxField\\index.jsx":"11","C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\api\\index.js":"12"},{"size":231,"mtime":1625822631025,"results":"13","hashOfConfig":"14"},{"size":533,"mtime":1625831714289,"results":"15","hashOfConfig":"14"},{"size":669,"mtime":1625914761557,"results":"16","hashOfConfig":"14"},{"size":3877,"mtime":1626058498870,"results":"17","hashOfConfig":"14"},{"size":240,"mtime":1625930751375,"results":"18","hashOfConfig":"14"},{"size":1949,"mtime":1625930688382,"results":"19","hashOfConfig":"14"},{"size":2546,"mtime":1626058418570,"results":"20","hashOfConfig":"14"},{"size":585,"mtime":1625882906064,"results":"21","hashOfConfig":"14"},{"size":679,"mtime":1625906627345,"results":"22","hashOfConfig":"14"},{"size":1832,"mtime":1625914851749,"results":"23","hashOfConfig":"14"},{"size":1013,"mtime":1625930391060,"results":"24","hashOfConfig":"14"},{"size":371,"mtime":1626058354639,"results":"25","hashOfConfig":"14"},{"filePath":"26","messages":"27","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},"1wj3y41",{"filePath":"29","messages":"30","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"31","messages":"32","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"33","messages":"34","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"35","messages":"36","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"37","messages":"38","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"39","messages":"40","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"41","messages":"42","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"43","messages":"44","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"45","messages":"46","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"47","messages":"48","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"49"},{"filePath":"50","messages":"51","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\index.jsx",[],["52","53","54","55","56","57"],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\App.jsx",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\pages\\profile\\index.jsx",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\Profile\\index.jsx",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\stores\\rootReducer.js",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\stores\\user\\userReducer.js",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\stores\\user\\userAction.js",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\tools\\api.js",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\TextField\\index.jsx",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\FileField\\index.jsx",[],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\componenets\\CheckBoxField\\index.jsx",[],["58","59","60","61","62","63"],"C:\\Users\\River\\Desktop\\Coding_test_thm\\frontend\\src\\api\\index.js",[],{"ruleId":"64","replacedBy":"65"},{"ruleId":"66","replacedBy":"67"},{"ruleId":"68","replacedBy":"69"},{"ruleId":"70","replacedBy":"71"},{"ruleId":"72","replacedBy":"73"},{"ruleId":"74","replacedBy":"75"},{"ruleId":"64","replacedBy":"76"},{"ruleId":"66","replacedBy":"77"},{"ruleId":"68","replacedBy":"78"},{"ruleId":"70","replacedBy":"79"},{"ruleId":"72","replacedBy":"80"},{"ruleId":"74","replacedBy":"81"},"lines-around-directive",["82"],"no-spaced-func",["83"],"global-require",[],"no-buffer-constructor",[],"no-new-require",[],"no-path-concat",[],["82"],["83"],[],[],[],[],"padding-line-between-statements","func-call-spacing"] \ No newline at end of file