diff --git a/javascript-practice/src/assets/images/favicon.ico b/javascript-practice/src/assets/images/favicon.ico new file mode 100644 index 0000000..f77999d Binary files /dev/null and b/javascript-practice/src/assets/images/favicon.ico differ diff --git a/javascript-practice/src/js/constants/constant.js b/javascript-practice/src/js/constants/constant.js index e6a63f2..9a05204 100644 --- a/javascript-practice/src/js/constants/constant.js +++ b/javascript-practice/src/js/constants/constant.js @@ -4,7 +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: 'Email or password not match! 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/loginController.js b/javascript-practice/src/js/controllers/loginController.js index a2ae10d..322b537 100644 --- a/javascript-practice/src/js/controllers/loginController.js +++ b/javascript-practice/src/js/controllers/loginController.js @@ -4,20 +4,13 @@ export default class LoginController { this.loginView = view.loginView; } - handlerGetUserByEmail(email) { - return this.service.userService.getUserByEmail(email); - } - - handlerCreateTokenUser(user) { - return this.service.userService.createTokenUser(user); + handlerValidateUser(email, password) { + return this.service.userService.validateUser(email, password); } init() { if (this.loginView.isLoginPage()) { - this.loginView.addHandlerForm( - this.handlerGetUserByEmail.bind(this), - this.handlerCreateTokenUser.bind(this), - ); + 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 f577ee1..7bb705a 100644 --- a/javascript-practice/src/js/helpers/helpers.js +++ b/javascript-practice/src/js/helpers/helpers.js @@ -50,3 +50,15 @@ export const createToken = () => { } 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/main.js b/javascript-practice/src/js/main.js index 626a1f8..851ad8e 100644 --- a/javascript-practice/src/js/main.js +++ b/javascript-practice/src/js/main.js @@ -3,5 +3,6 @@ import App from './app'; // Sure that scripts called after DOM loaded document.addEventListener('DOMContentLoaded', () => { const myApp = new App(); + myApp.start(); }); diff --git a/javascript-practice/src/js/models/user.js b/javascript-practice/src/js/models/user.js index 0d0536c..d46bb8a 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 = '', accessToken = '' }) { + 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 55c2415..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,59 @@ 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.defaultPath) { + /** + * 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); - await timeOutConnect(saveUser); + const results = convertModelToDataObject(model); + + const saveData = this.firebaseService.save( + results.data, + this.defaultPath + results.id, + ); + + await timeOutConnect(saveData); } + /** + * + * @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 result = await this.firebaseService.getDataFromId(id, path); const data = await timeOutConnect(result); + if (data) return data; + return null; } } 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 5d6e5a1..0bcaef8 100644 --- a/javascript-practice/src/js/services/userService.js +++ b/javascript-practice/src/js/services/userService.js @@ -1,5 +1,4 @@ import { createToken } from '../helpers/helpers'; -import User from '../models/user'; import CommonService from './commonService'; import LocalStorageService from './localStorageService'; @@ -15,17 +14,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); } /** @@ -34,10 +23,12 @@ 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; } + return false; } @@ -47,23 +38,49 @@ 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); - if (id) { - const user = await this.getDataFromId(id); - return new User(user); + const result = await this.getDataFromProp('email', email); + + if (result) { + return result; } + return null; } - async createTokenUser(user) { - const id = await this.getUserIdByEmail(user.email); + /** + * 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 - const newUserData = new User(user); newUserData.accessToken = createToken(); - this.save(newUserData, this.defaultPath + id); + this.save(newUserData); + // Add access token to local storage LocalStorageService.add('accessToken', newUserData.accessToken); } } diff --git a/javascript-practice/src/js/views/loginView.js b/javascript-practice/src/js/views/loginView.js index 2ac412e..0733561 100644 --- a/javascript-practice/src/js/views/loginView.js +++ b/javascript-practice/src/js/views/loginView.js @@ -1,21 +1,20 @@ import CommonLoginRegisterView from './commonLoginRegisterView'; -import * as CONSTANT from '../constants/constant'; +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.getElementById('loginPage'); + this.loginPage = document.URL.includes('/login'); } /** * Get data from user input * @returns {Object || null} Return object or null */ - getDataFromForm() { - const { loginForm } = document.forms; - const formData = new FormData(loginForm); + getDataFromForm(event) { + const formData = new FormData(event.target); const email = formData.get('email'); const password = formData.get('password'); @@ -28,12 +27,11 @@ export default class LoginView extends CommonLoginRegisterView { * Implement error popup in site * @param {string} content The content will show in error popup */ - initErrorPopup(content) { - const typePopup = CONSTANT.TYPE_POPUP.error; - const title = 'Error Credential!'; - 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.GOT_IT); // Show popup this.tooglePopupForm(); @@ -41,35 +39,36 @@ export default class LoginView extends CommonLoginRegisterView { /** * Add event listener for form input - * @param {Function} handler The function need to be set event + * @param {Function} validateUser The function need to be set event */ - addHandlerForm(getUserByEmail, createTokenUser) { + addHandlerForm(validateUser) { this.parentElement.addEventListener('submit', (e) => { e.preventDefault(); this.clearErrorMessage(); - this.submitForm(getUserByEmail, createTokenUser); + this.submitForm(validateUser, e); }); } - async submitForm(getUserByEmail, createTokenUser) { + /** + * 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(); + const userInput = this.getDataFromForm(event); // Check user exist - const user = await getUserByEmail(userInput.email); + const results = await validateUser(userInput.email, userInput.password); - if (user) { - // If user exist, compare password - if (userInput.password === user.password) { - await createTokenUser(user); - window.location.replace('/'); - return; - } + if (results) { + window.location.replace('/'); + + return; } - throw Error(CONSTANT.MESSAGE.ERROR_CREDENTIAL); + throw MESSAGE.ERROR_CREDENTIAL; } catch (error) { // Show popup error this.initErrorPopup(error); @@ -79,6 +78,6 @@ export default class LoginView extends CommonLoginRegisterView { } isLoginPage() { - return this.loginPage !== null; + return this.loginPage; } } diff --git a/javascript-practice/src/js/views/registerView.js b/javascript-practice/src/js/views/registerView.js index 3a26d6b..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'); } /** @@ -24,8 +24,10 @@ export default class RegisterView extends CommonLoginRegisterView { if (this.validateForm(account)) { const user = new User(account); + return user; } + return null; } @@ -48,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(); @@ -84,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 @@ -101,6 +102,6 @@ export default class RegisterView extends CommonLoginRegisterView { } isRegisterPage() { - return this.registerPage !== null; + return this.registerPage; } } diff --git a/javascript-practice/src/pages/login.html b/javascript-practice/src/pages/login.html index c502e11..bef7925 100644 --- a/javascript-practice/src/pages/login.html +++ b/javascript-practice/src/pages/login.html @@ -7,7 +7,7 @@