Merge pull request #9 from Nez27/feat/impl-register-view-and-user-service

Add register view and user service.
This commit is contained in:
Loi Phan
2023-08-30 15:48:14 +07:00
committed by GitHub
10 changed files with 371 additions and 20 deletions
@@ -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' };
-1
View File
@@ -1 +0,0 @@
console.log('Hello World!');
+20 -12
View File
@@ -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;
};
@@ -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(
@@ -0,0 +1,7 @@
import UserService from './userService';
export default class Service {
constructor() {
this.userService = new UserService();
}
}
@@ -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;
}
}
-1
View File
@@ -1 +0,0 @@
// TODO
@@ -0,0 +1,113 @@
import { MARK_ICON, TYPE_POPUP } from '../constants/constant';
export default class CommonView {
constructor() {
this.overlayMarkup = '<div class="overlay"></div>';
this.initPopup();
this.initElementPopup();
this.initLoader();
this.handleEventBtnPopupAndOverlay();
}
/**
* Implement popup in site
*/
initPopup() {
this.rootElement = document.querySelector('body');
const markup = `
${this.overlayMarkup}
<div class="modal-box">
<div class="mark"></div>
<h2 class="modal-box__title"></h2>
<p class="modal-box__message"></p>
<button class="modal-box__redirect-btn"></button>
</div>
`;
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 = `<div class="loader hidden"></div>`;
this.rootElement.insertAdjacentHTML('afterbegin', markup);
// Init element
this.spinner = document.querySelector('.loader');
}
}
@@ -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 = `
<p class="form__error-message">${
!message ? this.messageDefault : message
}</p>
`;
document
.querySelector('.form__title')
.insertAdjacentHTML('afterend', markup);
}
}
+7
View File
@@ -0,0 +1,7 @@
import RegisterView from './registerView';
export default class View {
constructor() {
this.registerView = new RegisterView();
}
}