diff --git a/javascript-practice/src/js/constants/constant.js b/javascript-practice/src/js/constants/constant.js index 08aa875..397a4ef 100644 --- a/javascript-practice/src/js/constants/constant.js +++ b/javascript-practice/src/js/constants/constant.js @@ -3,10 +3,7 @@ export const MESSAGE = { PASSWORD_NOT_STRONG: '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!', }; -export const TIME_OUT_ERROR = { - status: 408, - message: 'Connection time out! Please try again!', -}; -export const TYPE_FORM = { success: 'success', error: 'error' }; +export const TYPE_POPUP = { success: 'success', error: 'error' }; export const MARK_ICON = { success: 'check', error: 'error' }; diff --git a/javascript-practice/src/js/controller.js b/javascript-practice/src/js/controller.js index a420803..e69de29 100644 --- a/javascript-practice/src/js/controller.js +++ b/javascript-practice/src/js/controller.js @@ -1 +0,0 @@ -console.log('Hello World!'); diff --git a/javascript-practice/src/js/helpers/helpers.js b/javascript-practice/src/js/helpers/helpers.js index ab8e3a7..c6adaf1 100644 --- a/javascript-practice/src/js/helpers/helpers.js +++ b/javascript-practice/src/js/helpers/helpers.js @@ -1,29 +1,37 @@ -import { - TIME_OUT_SEC, - TIME_OUT_ERROR, - REGEX_PASSWORD, -} from '../constants/constant'; +import { MESSAGE } from '../constants/constant'; +import { REGEX_PASSWORD, TIME_OUT_SEC } from '../constants/config'; import FirebaseService from '../services/firebaseService'; +/** + * Validate password + * @param {string} password Password input + * @returns {boolean} Return true if validate password success, otherwise return false + */ export const validatePassword = (password) => { return REGEX_PASSWORD.test(password); }; +/** + * A waiting function with s second + * @param {number} s The time will be waiting + * @returns {Promise} A Promise will be only reject after s second + */ export const timeout = (s) => { - return new Promise((reject) => { + return new Promise((_, reject) => { setTimeout(() => { - reject(TIME_OUT_ERROR); + FirebaseService.disconnect(); + reject(MESSAGE.TIME_OUT_ERROR); }, s * 1000); }); }; +/** + * A function waiting the action need to be perform and throw error after s second. + * @param {Function} action The action need to be perform. + * @returns { Object || Error } Return the any object from Firebase or Error + */ export const timeOutConnect = async (action) => { const result = await Promise.race([action, timeout(TIME_OUT_SEC)]); - if (result && result.status === TIME_OUT_ERROR.status) { - FirebaseService.disconnect(); - throw new Error(`${result.message}`); - } - return result; }; diff --git a/javascript-practice/src/js/services/firebaseService.js b/javascript-practice/src/js/services/firebaseService.js index 8e8ebef..700e5ad 100644 --- a/javascript-practice/src/js/services/firebaseService.js +++ b/javascript-practice/src/js/services/firebaseService.js @@ -12,24 +12,43 @@ import { DATABASE_URL } from '../constants/config'; class FirebaseService { constructor() { const firebaseConfig = { - DATABASE_URL, + databaseURL: DATABASE_URL, }; this.app = initializeApp(firebaseConfig); this.db = getDatabase(this.app); } + /** + * Save data in database + * @param {Object} data The object need to save into database + * @param {string} path The path of database need to be save + * @returns {Promise} Return the relsoves when write to database completed + */ save(data, path) { return set(ref(this.db, path), data); } + /** + * Disconnect to database + */ disconnect() { goOffline(this.db); } + /** + * Reconnect to database + */ reconnect() { goOnline(this.db); } + /** + * Find the key 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) { return new Promise((resolve) => { onValue( diff --git a/javascript-practice/src/js/services/service.js b/javascript-practice/src/js/services/service.js new file mode 100644 index 0000000..dd10508 --- /dev/null +++ b/javascript-practice/src/js/services/service.js @@ -0,0 +1,7 @@ +import UserService from './userService'; + +export default class Service { + constructor() { + this.userService = new UserService(); + } +} diff --git a/javascript-practice/src/js/services/userService.js b/javascript-practice/src/js/services/userService.js new file mode 100644 index 0000000..65341ba --- /dev/null +++ b/javascript-practice/src/js/services/userService.js @@ -0,0 +1,39 @@ +import { timeOutConnect } from '../helpers/helpers'; +import FirebaseService from './firebaseService'; +import User from '../models/userModel'; + +export default class UserService { + constructor() { + this.path = 'users/'; + } + + /** + * Save user into database + * @param {Object} user The user object need to be saved into databae + */ + async saveUser(user) { + FirebaseService.reconnect(); + const saveUser = FirebaseService.save( + user, + this.path + User.createIdUser(), + ); + await timeOutConnect(saveUser); + } + + /** + * Check user exist in database by email + * @param {string} email Email need to be check + * @returns {boolean} Return true if email exist and otherwise is false + */ + async checkExistUserByEmail(email) { + FirebaseService.reconnect(); + const existUser = FirebaseService.findKeyByPropery( + this.path, + 'email', + email, + ); + const result = await timeOutConnect(existUser); + + return result; + } +} diff --git a/javascript-practice/src/js/view/view.js b/javascript-practice/src/js/view/view.js deleted file mode 100644 index 70b786d..0000000 --- a/javascript-practice/src/js/view/view.js +++ /dev/null @@ -1 +0,0 @@ -// TODO diff --git a/javascript-practice/src/js/views/commonView.js b/javascript-practice/src/js/views/commonView.js new file mode 100644 index 0000000..083f01f --- /dev/null +++ b/javascript-practice/src/js/views/commonView.js @@ -0,0 +1,113 @@ +import { MARK_ICON, TYPE_POPUP } from '../constants/constant'; + +export default class CommonView { + constructor() { + this.overlayMarkup = '
'; + + this.initPopup(); + this.initElementPopup(); + this.initLoader(); + this.handleEventBtnPopupAndOverlay(); + } + + /** + * Implement popup in site + */ + initPopup() { + this.rootElement = document.querySelector('body'); + + const markup = ` + ${this.overlayMarkup} + + `; + + this.rootElement.insertAdjacentHTML('afterbegin', markup); + } + + /** + * Assgign element in popup to property + */ + initElementPopup() { + this.modalBox = document.querySelector('.modal-box'); + this.popupIcon = document.querySelector('.mark'); + this.popupBtn = document.querySelector('.modal-box__redirect-btn'); + this.popupTitle = document.querySelector('.modal-box__title'); + this.popupContent = document.querySelector('.modal-box__message'); + this.overlay = document.querySelector('.overlay'); + } + + /** + * Show or hide loader screen + */ + toogleLoaderSpinner() { + this.spinner.classList.toggle('hidden'); + } + + /** + * Show or hide popup + */ + tooglePopupForm() { + this.overlay.classList.toggle('active'); + this.modalBox.classList.toggle('active'); + } + + /** + * Add popup content + * @param {TYPE_POPUP} typePopup Type of the popup + * @param {string} title Title of popup + * @param {string} content Content of popup + * @param {string} btnContent Content of button + */ + initPopupContent(typePopup, title, content, btnContent) { + // Remove old typePopup class if haved + this.modalBox.classList.forEach((classItem) => + classItem === TYPE_POPUP.success || classItem === TYPE_POPUP.error + ? this.modalBox.classList.remove(classItem) + : '', + ); + + // Remove old icon popup if haved + this.popupIcon.classList.forEach((classItem) => + classItem === MARK_ICON.success || classItem === MARK_ICON.error + ? this.popupIcon.classList.remove(classItem) + : '', + ); + + // Init content popup + this.modalBox.classList.add( + typePopup === TYPE_POPUP.success ? TYPE_POPUP.success : TYPE_POPUP.error, + ); + this.popupIcon.classList.add( + typePopup === TYPE_POPUP.success ? MARK_ICON.success : MARK_ICON.error, + ); + this.popupTitle.textContent = title; + this.popupContent.textContent = content; + this.popupBtn.textContent = btnContent; + } + + /** + * Add event listener for popup and overlay + */ + handleEventBtnPopupAndOverlay() { + this.popupBtn.addEventListener('click', this.tooglePopupForm.bind(this)); + this.overlay.addEventListener('click', this.tooglePopupForm.bind(this)); + } + + /** + * Implement loader screen + */ + initLoader() { + const markup = ``; + + this.rootElement.insertAdjacentHTML('afterbegin', markup); + + // Init element + this.spinner = document.querySelector('.loader'); + } +} diff --git a/javascript-practice/src/js/views/registerView.js b/javascript-practice/src/js/views/registerView.js new file mode 100644 index 0000000..aa0576e --- /dev/null +++ b/javascript-practice/src/js/views/registerView.js @@ -0,0 +1,163 @@ +import { validatePassword } from '../helpers/helpers'; +import * as CONSTANT from '../constants/constant'; +import CommonView from './commonView'; +import User from '../models/userModel'; + +export default class RegisterView extends CommonView { + constructor() { + super(); + + this.parentElement = document.querySelector('.form'); + this.inputField = document.querySelectorAll('.form__input'); + + this.messageDefault = CONSTANT.MESSAGE.ERROR_MESSAGE_DEFAULT; + } + + /** + * Get data from user input + * @returns {Object || null} Return user object or null + */ + getDataFromForm() { + const { registerForm } = document.forms; + const formData = new FormData(registerForm); + const email = formData.get('email'); + const password = formData.get('password'); + const passwordConfirm = formData.get('password_confirm'); + + const account = { email, password, passwordConfirm }; + + if (this.validateForm(account)) { + const user = new User(account); + return user; + } + return null; + } + + /** + * Validate user input data + * @param {Object} account The account object with email, password, passwordConfirm field + * @returns {boolean} Return true if validate success and return false if validate not success + */ + validateForm(account) { + if (account.password === account.passwordConfirm) { + if (validatePassword(account.passwordConfirm)) { + return true; + } + this.showError(CONSTANT.MESSAGE.PASSWORD_NOT_STRONG); + return false; + } + this.showError(CONSTANT.MESSAGE.PASSWORD_NOT_MATCH); + return false; + } + + /** + * Add event listener for form input + * @param {Function} handler The function need to be set event + */ + addHandlerForm(handler) { + this.parentElement.addEventListener('submit', (e) => { + e.preventDefault(); + this.clearErrorMessage(); + handler(); + }); + } + + /** + * Show error style input password + */ + changeToStyleErrorInputPassword() { + // Index = 1 because it should skip email input field + for (let index = 1; index < this.inputField.length; index += 1) { + this.inputField[index].style.background = '#ffc3c3'; + this.inputField[index].style.borderColor = '#ce1414'; + } + } + + /** + * Clear error style input password + */ + clearStyleErrorInputPassword() { + for (let index = 1; index < this.inputField.length; index += 1) { + this.inputField[index].style.background = '#f5f5f5'; + this.inputField[index].style.borderColor = '#f5f5f5'; + } + } + + /** + * Reassign again to check error message element haved on page or not + */ + reassignVariableErrorMessage() { + this.errorMessage = document.querySelector('.form__error-message'); + } + + /** + * Clear error message at form + */ + clearErrorMessage() { + this.reassignVariableErrorMessage(); + + // If have error message on page, remove it + if (this.errorMessage) { + this.errorMessage.remove(); + this.clearStyleErrorInputPassword(); + } + } + + /** + * Add event listener for input field at form + */ + addHandlerInputFormChange() { + this.parentElement.addEventListener('input', () => { + this.clearErrorMessage(); + }); + } + + /** + * Show error message with error style input password. + * @param {string} message The error message you want show in form. + */ + showError(message) { + this.renderError(message); + this.changeToStyleErrorInputPassword(); + } + + /** + * Implement register success popup in site + */ + initRegisterSuccessPopup() { + const typePopup = CONSTANT.TYPE_POPUP.success; + const title = 'Register Commpleted'; + const content = 'Please login to continue!'; + const btnContent = 'OK'; + + this.initPopupContent(typePopup, title, content, btnContent); + } + + /** + * 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'; + const btnContent = 'Got it!'; + + this.initPopupContent(typePopup, title, content, btnContent); + } + + /** + * Show error message in form + * @param {*} message The message will show in form + */ + renderError(message) { + const markup = ` +

${ + !message ? this.messageDefault : message + }

+ `; + + document + .querySelector('.form__title') + .insertAdjacentHTML('afterend', markup); + } +} diff --git a/javascript-practice/src/js/views/view.js b/javascript-practice/src/js/views/view.js new file mode 100644 index 0000000..a2a1675 --- /dev/null +++ b/javascript-practice/src/js/views/view.js @@ -0,0 +1,7 @@ +import RegisterView from './registerView'; + +export default class View { + constructor() { + this.registerView = new RegisterView(); + } +}