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();
+ }
+}