Add login method

This commit is contained in:
2023-09-30 16:18:37 +07:00
parent 1d13278f8c
commit 6fa2d772b7
16 changed files with 256 additions and 52 deletions
+1
View File
@@ -44,5 +44,6 @@
</div>
</div>
</main>
<script type="module" src="../ts/index.ts"></script>
</body>
</html>
+4 -3
View File
@@ -3,13 +3,14 @@ import Service from './services/index';
import View from './views/index';
export default class App {
private controller: Controller;
private _controller: Controller;
constructor() {
this.controller = new Controller(new Service(), new View());
this._controller = new Controller(new Service(), new View());
}
start() {
this.controller.registerController.init();
this._controller.registerController.init();
this._controller.loginController.init();
}
}
@@ -26,3 +26,9 @@ export const REGEX = {
EMAIL:
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|.(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
};
export const URL = {
LOGIN: 'login',
REGISTER: 'register',
HOME: '',
};
@@ -1,14 +1,18 @@
import Service from 'services';
import RegisterController from './registerController';
import View from 'views';
import LoginController from './loginController';
export default class Controller {
public registerController: RegisterController;
public loginController: LoginController;
constructor(
public service: Service,
public view: View,
) {
this.registerController = new RegisterController(service, view);
this.loginController = new LoginController(service, view);
}
}
@@ -0,0 +1,31 @@
import User from 'models/user';
import Service from 'services';
import View from 'views';
import LoginView from 'views/loginView';
export default class LoginController {
public loginView: LoginView | null = null;
constructor(
public service: Service,
public view: View,
) {
this.service = service;
this.loginView = view.loginView;
}
handlerLoginUser(email: string, password: string): Promise<boolean> {
return this.service.userService.loginUser(email, password);
}
handlerGetInfoUserLogin(): Promise<User | null> {
return this.service.userService.getInfoUserLogin();
}
init() {
if (this.loginView) {
this.loginView.loadPage(this.handlerGetInfoUserLogin.bind(this));
this.loginView.addHandlerForm(this.handlerLoginUser.bind(this));
}
}
}
@@ -4,14 +4,14 @@ import View from 'views';
import RegisterView from 'views/registerView';
export default class RegisterController {
public registerView: RegisterView;
public registerView: RegisterView | null = null;
constructor(
public service: Service,
public view: View,
) {
this.registerView = view.registerView;
this.service = service;
this.registerView = view.registerView;
}
handlerCheckUserValid(email: string): Promise<boolean> {
@@ -18,3 +18,12 @@ export type TError = {
title: string;
message: string;
};
export class CustomError extends Error {
constructor(
public title: string,
message?: string,
) {
super(message);
}
}
@@ -1,4 +0,0 @@
// eslint-disable-next-line import/prefer-default-export
export const redirectToLoginPage = (): void => {
window.location.replace('/login');
};
+16
View File
@@ -0,0 +1,16 @@
export const redirectToLoginPage = (): void => {
window.location.replace('/login');
};
export const getSubdirectoryURL = () => {
const url = window.location.href;
const parts = url.split('/'); // Results: ['http:', '', 'example.com', '']
const subDirectory = parts[3]; // Get subdirectory url only
const index = subDirectory.indexOf('?'); // Remove query behind subDirectory
if (index !== -1) {
return subDirectory.substring(0, index);
}
return subDirectory;
};
+18 -18
View File
@@ -1,38 +1,38 @@
import { createIdUser } from '../helpers/data';
export default class User {
private readonly id: number;
private readonly _id: number;
private email: string;
private _email: string;
private password: string;
private _password: string;
private accessToken: string;
private _accessToken: string;
constructor(email: string, password: string, accessToken?: string) {
this.id = createIdUser();
this.email = email;
this.password = password || '';
this.accessToken = accessToken || '';
this._id = createIdUser();
this._email = email;
this._password = password || '';
this._accessToken = accessToken || '';
}
get getPassword() {
return this.password;
get password() {
return this._password;
}
set setAccessToken(accessToken: string) {
this.accessToken = accessToken;
set accessToken(accessToken: string) {
this._accessToken = accessToken;
}
get getAccessToken() {
return this.accessToken;
get accessToken() {
return this._accessToken;
}
get getEmail() {
return this.email;
get email() {
return this._email;
}
get getId() {
return this.id;
get id() {
return this._id;
}
}
@@ -12,16 +12,16 @@ import {
import { DATABASE_URL } from '../constants/config';
class FirebaseService {
private app: FirebaseApp;
private _app: FirebaseApp;
private db: Database;
private _db: Database;
constructor() {
const firebaseConfig = {
databaseURL: DATABASE_URL,
};
this.app = initializeApp(firebaseConfig);
this.db = getDatabase(this.app);
this._app = initializeApp(firebaseConfig);
this._db = getDatabase(this._app);
}
/**
@@ -31,25 +31,25 @@ class FirebaseService {
* @returns {Promise} Return the resolves when write to database completed
*/
save(data: object, path: string): Promise<void> {
return set(ref(this.db, path), data);
return set(ref(this._db, path), data);
}
delete(id: string, path: string): Promise<void> {
return remove(ref(this.db, path + id));
return remove(ref(this._db, path + id));
}
/**
* Disconnect to database
*/
disconnect(): void {
goOffline(this.db);
goOffline(this._db);
}
/**
* Reconnect to database
*/
reconnect(): void {
goOnline(this.db);
goOnline(this._db);
}
/**
@@ -66,7 +66,7 @@ class FirebaseService {
): Promise<object | null> {
return new Promise((resolve) => {
onValue(
ref(this.db, path),
ref(this._db, path),
(snapshot) => {
let id: string | null = null;
let data: object | null = null;
@@ -104,7 +104,7 @@ class FirebaseService {
getDataFromId(id: string, path: string): Promise<object> {
return new Promise((resolve) => {
onValue(
ref(this.db, path + id),
ref(this._db, path + id),
(snapshot) => {
resolve(snapshot.val());
},
@@ -118,7 +118,7 @@ class FirebaseService {
getAllDataFromPath(path: string): Promise<object[]> {
return new Promise((resolve) => {
onValue(
ref(this.db, path),
ref(this._db, path),
(snapshot) => {
const listData: object[] = [];
@@ -149,7 +149,7 @@ class FirebaseService {
): Promise<object[]> {
return new Promise((resolve) => {
onValue(
ref(this.db, path),
ref(this._db, path),
(snapshot) => {
let id: string;
let data: object;
@@ -1,24 +1,24 @@
class LocalStorageService {
private localStorage: Storage;
private _localStorage: Storage;
constructor() {
this.localStorage = localStorage;
this._localStorage = localStorage;
}
add(key: string, value: string): void {
this.localStorage.setItem(key, value);
this._localStorage.setItem(key, value);
}
get(key: string): string | null {
return this.localStorage.getItem(key);
return this._localStorage.getItem(key);
}
remove(key: string): void {
this.localStorage.removeItem(key);
this._localStorage.removeItem(key);
}
clear(): void {
this.localStorage.clear();
this._localStorage.clear();
}
}
@@ -51,7 +51,7 @@ export default class UserService {
const user = await this.getUserByEmail(email);
// Check password
if (user && user.getPassword === password) {
if (user && user.password === password) {
// Create token for user
await this.createTokenUser(email);
@@ -71,14 +71,14 @@ export default class UserService {
const newUserData = user;
// Add token to user object
newUserData.setAccessToken = createToken();
newUserData.accessToken = createToken();
this._commonService.save(newUserData);
// Add access token to local storage
LocalStorageService.add(
LOCAL_STORAGE.ACCESS_TOKEN,
newUserData.getAccessToken,
newUserData.accessToken,
);
}
}
+15 -2
View File
@@ -1,9 +1,22 @@
import { getSubdirectoryURL } from 'helpers/url';
import LoginView from './loginView';
import RegisterView from './registerView';
import { URL } from 'constants/config';
export default class View {
public registerView: RegisterView;
public registerView: RegisterView | null = null;
public loginView: LoginView | null = null;
constructor() {
this.registerView = new RegisterView();
switch (getSubdirectoryURL()) {
case URL.LOGIN:
this.loginView = new LoginView();
break;
case URL.REGISTER:
this.registerView = new RegisterView();
break;
default:
}
}
}
@@ -0,0 +1,127 @@
import AuthenticationView from './authenticationView';
import { TypeToast, BTN_CONTENT } from '../constants/config';
import User from 'models/user';
import { DEFAULT_TITLE_ERROR_TOAST } from 'constants/messages/dialog';
import { CustomError, TError } from 'global/types';
import { redirectToLoginPage } from 'helpers/url';
import { ERROR_CREDENTIAL } from 'constants/messages/form';
export default class LoginView extends AuthenticationView {
constructor() {
super();
this.initToast();
this.initLoader();
this.handleEventToast();
}
async loadPage(getInfoUserLogin: () => Promise<User | null>) {
this.toggleLoaderSpinner();
const user = await getInfoUserLogin();
if (user) {
window.location.replace('/');
}
this.toggleLoaderSpinner();
}
/**
* Implement error toast in site
* @param {string} content The content will show in error toast
*/
initErrorToast(error: TError | string): void {
const title =
typeof error === 'object' && error.title
? error.title
: DEFAULT_TITLE_ERROR_TOAST;
const content =
typeof error === 'object' && error.message
? error.message
: (error as string);
this.initToastContent(TypeToast.error, title, content, BTN_CONTENT.OK);
if (this.toastDialog && this.toastBtn) {
// Show toast
this.toastDialog.showModal();
// Remove event for toast button
this.toastBtn.removeEventListener('click', redirectToLoginPage);
}
}
/**
* Add event listener for form input
* @param {Function} validateUser The function need to be set event
*/
addHandlerForm(
loginUser: (email: string, password: string) => Promise<boolean>,
) {
if (this.formEl)
this.formEl.addEventListener('submit', (e: Event) => {
e.preventDefault();
this.clearErrorMessage();
this.submitForm(loginUser, e);
});
}
/**
* The action when submit form
* @param {Function} loginUser The function need to be set event
* * @param {event} event The event target
*/
async submitForm(
loginUser: (email: string, password: string) => Promise<boolean>,
event: Event,
) {
try {
// Load spinner
this.toggleLoaderSpinner();
// Get data from form
const userInput = this.validateForm(event);
if (userInput) {
// Check user exist
const results = await loginUser(userInput.email, userInput.password);
if (results) {
window.location.replace('/');
return;
}
throw new CustomError(ERROR_CREDENTIAL.title, ERROR_CREDENTIAL.message);
}
} catch (error) {
// Show toast error
this.initErrorToast(error as string | TError);
}
// Close spinner
this.toggleLoaderSpinner();
}
/**
* Get data from user input
* @returns {Object || null} Return object or null
*/
validateForm(event: Event): User | null {
if (event.target) {
const formData = new FormData(event.target as HTMLFormElement);
const email = formData.get('email') as string;
const password = formData.get('password') as string;
// Validate user input
this.listError = []; // Reset list error
const emailValid = this.validateEmail(email);
const passwordValid = this.validatePassword(password);
// Show error style
if (this.emailEl && this.inputPasswordEl) {
this.emailEl.classList.toggle('error-input', !emailValid);
this.inputPasswordEl.classList.toggle('error-input', !passwordValid);
if (emailValid && passwordValid) {
return new User(email, password);
}
}
}
this.showError(this.listError);
return null;
}
}
@@ -1,7 +1,7 @@
import { TypeToast, BTN_CONTENT } from '../constants/config';
import AuthenticationView from './authenticationView';
import User from '../models/user';
import { redirectToLoginPage } from '../helpers/redirect';
import { redirectToLoginPage } from '../helpers/url';
import {
DEFAULT_MESSAGE,
DEFAULT_TITLE_ERROR_TOAST,
@@ -148,7 +148,7 @@ export default class RegisterView extends AuthenticationView {
// Save user
if (user) {
// Check user exist
const userExist = await checkExistUser(user.getEmail);
const userExist = await checkExistUser(user.email);
if (userExist) {
throw Error(USER_EXIST_ERROR);
} else {