diff --git a/javascript-practice/src/js/app.js b/javascript-practice/src/js/app.js index 6c68c70..c3f6fc3 100644 --- a/javascript-practice/src/js/app.js +++ b/javascript-practice/src/js/app.js @@ -11,5 +11,6 @@ export default class App { start() { this.controller.registerController.init(); + this.controller.loginController.init(); } } diff --git a/javascript-practice/src/js/constants/constant.js b/javascript-practice/src/js/constants/constant.js index 397a4ef..9a05204 100644 --- a/javascript-practice/src/js/constants/constant.js +++ b/javascript-practice/src/js/constants/constant.js @@ -4,6 +4,16 @@ export const MESSAGE = { 'Password must at least one uppercase, one lowercase letter, one number and one special character!', ERROR_MESSAGE_DEFAULT: 'Something went wrong!', TIME_OUT_ERROR: 'Connection time out! Please try again!', + ERROR_CREDENTIAL: { + title: 'Error Credential', + message: 'Email or password not match! Please try again!', + }, + DEFAULT_TITLE_ERROR_POPUP: 'Error', + USER_EXIST_ERROR: 'User is exists! Please try another email!', +}; +export const BTN_CONTENT = { + GOT_IT: 'Got it!', + OK: 'Ok', }; export const TYPE_POPUP = { success: 'success', error: 'error' }; export const MARK_ICON = { success: 'check', error: 'error' }; diff --git a/javascript-practice/src/js/controllers/controller.js b/javascript-practice/src/js/controllers/controller.js index e360b3c..1434739 100644 --- a/javascript-practice/src/js/controllers/controller.js +++ b/javascript-practice/src/js/controllers/controller.js @@ -1,7 +1,9 @@ +import LoginController from './loginController'; import RegisterController from './registerController'; export default class Controller { constructor(service, view) { this.registerController = new RegisterController(service, view); + this.loginController = new LoginController(service, view); } } diff --git a/javascript-practice/src/js/controllers/loginController.js b/javascript-practice/src/js/controllers/loginController.js new file mode 100644 index 0000000..322b537 --- /dev/null +++ b/javascript-practice/src/js/controllers/loginController.js @@ -0,0 +1,16 @@ +export default class LoginController { + constructor(service, view) { + this.service = service; + this.loginView = view.loginView; + } + + handlerValidateUser(email, password) { + return this.service.userService.validateUser(email, password); + } + + init() { + if (this.loginView.isLoginPage()) { + this.loginView.addHandlerForm(this.handlerValidateUser.bind(this)); + } + } +} diff --git a/javascript-practice/src/js/helpers/helpers.js b/javascript-practice/src/js/helpers/helpers.js index c6adaf1..7bb705a 100644 --- a/javascript-practice/src/js/helpers/helpers.js +++ b/javascript-practice/src/js/helpers/helpers.js @@ -35,3 +35,30 @@ export const timeOutConnect = async (action) => { return result; }; + +/** + * A function create token for user + * @returns {string} Return token string + */ +export const createToken = () => { + const lengthToken = 36; + const chars = + 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + let token = ''; + for (let i = 0; i < lengthToken; i += 1) { + token += chars[Math.floor(Math.random() * chars.length)]; + } + return token; +}; + +export const convertModelToDataObject = (model) => { + const { id, ...data } = model; + + return { id, data }; +}; + +export const convertDataObjectToModel = (data) => { + const { id, ...object } = data; + + return { id, ...object.data }; +}; diff --git a/javascript-practice/src/js/models/user.js b/javascript-practice/src/js/models/user.js index 3e6ab49..68e3962 100644 --- a/javascript-practice/src/js/models/user.js +++ b/javascript-practice/src/js/models/user.js @@ -1,5 +1,6 @@ export default class User { constructor({ email, password, walletName = '' }) { + this.id = User.createIdUser(); this.email = email; this.password = password; this.walletName = walletName; diff --git a/javascript-practice/src/js/services/commonService.js b/javascript-practice/src/js/services/commonService.js index d5b0e0e..717d424 100644 --- a/javascript-practice/src/js/services/commonService.js +++ b/javascript-practice/src/js/services/commonService.js @@ -1,4 +1,8 @@ -import { timeOutConnect } from '../helpers/helpers'; +import { + convertDataObjectToModel, + convertModelToDataObject, + timeOutConnect, +} from '../helpers/helpers'; import FirebaseService from './firebaseService'; export default class CommonService { @@ -7,32 +11,56 @@ export default class CommonService { this.firebaseService = FirebaseService; } + /** + * Connect to Firebase Databse + */ connectToDb() { this.firebaseService.reconnect(); } - async findKeyByProperty(property, value, path = this.defaultPath) { + async getDataFromProp(property, value, path = this.defaultPath) { this.connectToDb(); - const existUser = this.firebaseService.findKeyByPropery( + const existUser = this.firebaseService.getDataFromProp( path, property, value, ); const result = await timeOutConnect(existUser); - return result; + if (result.id && result.data) { + return convertDataObjectToModel(result); + } + + return null; } - async save(data, path = this.path) { + /** + * Save data on database + * @param {*} data The data wants to save on database + * @param {string} path The path of database + */ + async save(model) { this.connectToDb(); - const saveUser = this.firebaseService.save(data, path); + const results = convertModelToDataObject(model); - await timeOutConnect(saveUser); + const saveData = this.firebaseService.save( + results.data, + this.defaultPath + results.id, + ); + + await timeOutConnect(saveData); } - async getDataFromId(id, path = this.path) { + /** + * + * @param {string} id The string of data object + * @param {string} path The path of database + * @returns {Object || null} Return the object if has, otherwise return null + */ + async getDataFromId(id, path = this.defaultPath) { this.connectToDb(); - const data = await this.firebaseService.getDataFromId(id, path); + const result = await this.firebaseService.getDataFromId(id, path); + const data = await timeOutConnect(result); if (data) return data; diff --git a/javascript-practice/src/js/services/firebaseService.js b/javascript-practice/src/js/services/firebaseService.js index d65b544..80d2036 100644 --- a/javascript-practice/src/js/services/firebaseService.js +++ b/javascript-practice/src/js/services/firebaseService.js @@ -43,28 +43,30 @@ class FirebaseService { } /** - * Find the key of value by property in database + * Find id of value by property in database * @param {string} path The path of database to be found * @param {string} property The property of the value need to be found * @param {value} value The value to compare in database * @returns {Promise} Return the relsoves when find completed */ - findKeyByPropery(path, property, value) { + getDataFromProp(path, property, value) { return new Promise((resolve) => { onValue( ref(this.db, path), (snapshot) => { - let result; + let id; + let data; // snapshot is a type of data by Firebase define snapshot.forEach((childSnapshot) => { - const data = childSnapshot.val(); + const dataTemp = childSnapshot.val(); - if (data[property] === value) { - result = childSnapshot.key; + if (dataTemp[property] === value) { + id = childSnapshot.key; + data = dataTemp; } }); - resolve(result); + resolve({ id, data }); }, { onlyOnce: true, @@ -73,6 +75,12 @@ class FirebaseService { }); } + /** + * Get data object from Id + * @param {string} id The id of data object + * @param {string} path The path of data save in database + * @returns {Promise} Return new Promise + */ getDataFromId(id, path) { return new Promise((resolve) => { onValue( diff --git a/javascript-practice/src/js/services/userService.js b/javascript-practice/src/js/services/userService.js index 34d9b40..86bf3fc 100644 --- a/javascript-practice/src/js/services/userService.js +++ b/javascript-practice/src/js/services/userService.js @@ -1,4 +1,4 @@ -import User from '../models/user'; +import { createToken } from '../helpers/helpers'; import CommonService from './commonService'; export default class UserService extends CommonService { @@ -13,18 +13,7 @@ export default class UserService extends CommonService { * @param {Object} user The user object need to be saved into databae */ saveUser(user) { - const pathData = this.defaultPath + User.createIdUser(); - - this.save(user, pathData); - } - - /** - * Get user id by email - * @param {string} email Email need to be check - * @returns {Promise || number} Return id user when exist, otherwise will undefined - */ - getUserIdByEmail(email) { - return this.findKeyByProperty('email', email); + this.save(user); } /** @@ -33,7 +22,7 @@ export default class UserService extends CommonService { * @returns {boolean} Return true if find, otherwise return false */ async checkUserExist(email) { - const userExist = await this.getUserIdByEmail(email); + const userExist = await this.getUserByEmail(email); if (userExist) { return true; @@ -48,14 +37,45 @@ export default class UserService extends CommonService { * @returns {Object || null} Return new User Object if find, otherwise return null. */ async getUserByEmail(email) { - const id = await this.getUserIdByEmail(email); + const result = await this.getDataFromProp('email', email); - if (id) { - const user = await this.getDataFromId(id); - - return new User(user); + if (result) { + return result; } return null; } + + /** + * Validate user info + * @param {string} email The email user input + * @param {*} password The password user input + * @returns {boolean} Return true if match info on database, otherwise return false + */ + async validateUser(email, password) { + const user = await this.getUserByEmail(email); + + // Check password + if (user && user.password === password) { + // Create token for user + await this.createTokenUser(email); + + return true; + } + + return false; + } + + /** + * Create token for user on database + * @param {string} email The email user need to be create token + */ + async createTokenUser(email) { + const user = await this.getUserByEmail(email); + const newUserData = user; + + // Add token to user object + newUserData.accessToken = createToken(); + this.save(newUserData); + } } diff --git a/javascript-practice/src/js/views/loginView.js b/javascript-practice/src/js/views/loginView.js new file mode 100644 index 0000000..0733561 --- /dev/null +++ b/javascript-practice/src/js/views/loginView.js @@ -0,0 +1,83 @@ +import CommonLoginRegisterView from './commonLoginRegisterView'; +import { MESSAGE, TYPE_POPUP, BTN_CONTENT } from '../constants/constant'; + +export default class LoginView extends CommonLoginRegisterView { + constructor() { + super(); + + this.parentElement = document.querySelector('.form'); + this.loginPage = document.URL.includes('/login'); + } + + /** + * Get data from user input + * @returns {Object || null} Return object or null + */ + getDataFromForm(event) { + const formData = new FormData(event.target); + const email = formData.get('email'); + const password = formData.get('password'); + + this.account = { email, password }; + + return this.account; + } + + /** + * Implement error popup in site + * @param {string} content The content will show in error popup + */ + initErrorPopup(error) { + const title = error.title ? error.title : MESSAGE.DEFAULT_TITLE_ERROR_POPUP; + const content = error.message ? error.message : error; + + this.initPopupContent(TYPE_POPUP.error, title, content, BTN_CONTENT.GOT_IT); + + // Show popup + this.tooglePopupForm(); + } + + /** + * Add event listener for form input + * @param {Function} validateUser The function need to be set event + */ + addHandlerForm(validateUser) { + this.parentElement.addEventListener('submit', (e) => { + e.preventDefault(); + this.clearErrorMessage(); + this.submitForm(validateUser, e); + }); + } + + /** + * The action when submit form + * @param {Function} validateUser The function need to be set event + */ + async submitForm(validateUser, event) { + try { + // Load spinner + this.toogleLoaderSpinner(); + + // Get data from form + const userInput = this.getDataFromForm(event); + // Check user exist + const results = await validateUser(userInput.email, userInput.password); + + if (results) { + window.location.replace('/'); + + return; + } + throw MESSAGE.ERROR_CREDENTIAL; + } catch (error) { + // Show popup error + this.initErrorPopup(error); + } + // Close spinner + this.toogleLoaderSpinner(); + } + + isLoginPage() { + return this.loginPage; + } +} diff --git a/javascript-practice/src/js/views/registerView.js b/javascript-practice/src/js/views/registerView.js index b0fb0c0..7470dd2 100644 --- a/javascript-practice/src/js/views/registerView.js +++ b/javascript-practice/src/js/views/registerView.js @@ -1,4 +1,4 @@ -import { TYPE_POPUP } from '../constants/constant'; +import { TYPE_POPUP, MESSAGE, BTN_CONTENT } from '../constants/constant'; import CommonLoginRegisterView from './commonLoginRegisterView'; import User from '../models/user'; @@ -6,7 +6,7 @@ export default class RegisterView extends CommonLoginRegisterView { constructor() { super(); - this.registerPage = document.getElementById('registerPage'); + this.registerPage = document.URL.includes('/register'); } /** @@ -50,12 +50,11 @@ export default class RegisterView extends CommonLoginRegisterView { * Implement error popup in site * @param {string} content The content will show in error popup */ - initErrorPopup(content) { - const typePopup = TYPE_POPUP.error; - const title = 'Error'; - const btnContent = 'Got it!'; + initErrorPopup(error) { + const title = error.title ? error.title : MESSAGE.DEFAULT_TITLE_ERROR_POPUP; + const content = error.message ? error.message : error; - this.initPopupContent(typePopup, title, content, btnContent); + this.initPopupContent(TYPE_POPUP.error, title, content, BTN_CONTENT.OK); // Show popup this.tooglePopupForm(); @@ -86,7 +85,7 @@ export default class RegisterView extends CommonLoginRegisterView { // Check user exist const userExist = await checkExistUser(user.email); if (userExist) { - throw Error('User is exists! Please try another email!'); + throw Error(MESSAGE.USER_EXIST_ERROR); } else { await saveUser(user); // Show popup success @@ -103,6 +102,6 @@ export default class RegisterView extends CommonLoginRegisterView { } isRegisterPage() { - return this.registerPage !== null; + return this.registerPage; } } diff --git a/javascript-practice/src/js/views/view.js b/javascript-practice/src/js/views/view.js index a2a1675..333d0a7 100644 --- a/javascript-practice/src/js/views/view.js +++ b/javascript-practice/src/js/views/view.js @@ -1,7 +1,9 @@ import RegisterView from './registerView'; +import LoginView from './loginView'; export default class View { constructor() { this.registerView = new RegisterView(); + this.loginView = new LoginView(); } } diff --git a/javascript-practice/src/pages/login.html b/javascript-practice/src/pages/login.html index 4c19064..bef7925 100644 --- a/javascript-practice/src/pages/login.html +++ b/javascript-practice/src/pages/login.html @@ -7,7 +7,7 @@