Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(RSS-ECOMM-2_55): router component #207

Merged
merged 10 commits into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 16 additions & 18 deletions src/app/App/model/AppModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,31 +17,29 @@ class AppModel {
private router = new RouterModel();

constructor() {
this.router.setPages(this.initPages());
document.body.append(this.appView.getHTML());
this.appView.getHTML().insertAdjacentElement('beforebegin', new HeaderModel(this.router).getHTML());
this.appView.getHTML().insertAdjacentElement('afterend', new FooterModel(this.router).getHTML());
this.router.setRoutes(this.createRoutes());
}

private initPages(): Map<string, Page> {
const root = this.getHTML();
root.append(new HeaderModel(this.router).getHTML());
const loginPage = new LoginPageModel(root, this.router);
const mainPage = new MainPageModel(root, this.router);
const registrationPage = new RegistrationPageModel(root, this.router);
const notFoundPage = new NotFoundPageModel(root, this.router);
const pages: Map<string, Page> = new Map(
private createRoutes(): Map<string, () => Page> {
return new Map(
Object.entries({
[PAGE_ID.DEFAULT_PAGE]: mainPage,
[PAGE_ID.LOGIN_PAGE]: loginPage,
[PAGE_ID.MAIN_PAGE]: mainPage,
[PAGE_ID.NOT_FOUND_PAGE]: notFoundPage,
[PAGE_ID.REGISTRATION_PAGE]: registrationPage,
[PAGE_ID.DEFAULT_PAGE]: () => new MainPageModel(this.appView.getHTML()),
[PAGE_ID.LOGIN_PAGE]: () => new LoginPageModel(this.appView.getHTML(), this.router),
[PAGE_ID.MAIN_PAGE]: () => new MainPageModel(this.appView.getHTML()),
[PAGE_ID.NOT_FOUND_PAGE]: () => new NotFoundPageModel(this.appView.getHTML(), this.router),
[PAGE_ID.REGISTRATION_PAGE]: () => new RegistrationPageModel(this.appView.getHTML(), this.router),
}),
);
root.append(new FooterModel(this.router).getHTML());
return pages;
}

public getHTML(): HTMLDivElement {
return this.appView.getHTML();
public start(): boolean {
if (!this.appView.getHTML()) {
return false;
}
return true;
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/app/App/tests/App.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import AppModel from '../model/AppModel.ts';
const app = new AppModel();

describe('Checking AppModel class', () => {
it('the getHTML method should return HTMLDivElement', () => {
expect(app.getHTML()).toBeInstanceOf(HTMLDivElement);
it('application successfully created', () => {
expect(app.start()).toBe(true);
});
});
5 changes: 3 additions & 2 deletions src/app/App/view/appView.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
position: relative;
display: flex;
flex-direction: column;
justify-content: space-between;
justify-content: center;
margin: 0 auto;
padding: 60px 0;
width: 100%;
min-height: 100vh;
min-height: calc(100vh - 141px);
max-width: 1440px;
}
55 changes: 23 additions & 32 deletions src/app/Router/model/RouterModel.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import type { Page } from '@/shared/types/common.ts';

import EventMediatorModel from '@/shared/EventMediator/model/EventMediatorModel.ts';
import MEDIATOR_EVENT from '@/shared/constants/events.ts';
import { PAGE_ID } from '@/shared/constants/pages.ts';

const DEFAULT_SEGMENT = import.meta.env.VITE_APP_DEFAULT_SEGMENT;
Expand All @@ -10,53 +8,46 @@ const PATH_SEGMENTS_TO_KEEP = import.meta.env.VITE_APP_PATH_SEGMENTS_TO_KEEP;
const PROJECT_TITLE = import.meta.env.VITE_APP_PROJECT_TITLE;

class RouterModel {
private eventMediator = EventMediatorModel.getInstance();

private pages: Map<string, Page> = new Map();
private routes: Map<string, () => Page> = new Map();

constructor() {
document.addEventListener('DOMContentLoaded', () => {
const currentPath = window.location.pathname
.split(DEFAULT_SEGMENT)
.slice(PATH_SEGMENTS_TO_KEEP + NEXT_SEGMENT)
.join(DEFAULT_SEGMENT);
this.handleRequest(currentPath);
this.navigateTo(currentPath);
});
}

private handleRequest(path: string): null | string {
const pathParts = path.split(DEFAULT_SEGMENT);
const hasRoute = this.pages.has(pathParts.join(''));
if (!hasRoute) {
document.title = `${PROJECT_TITLE} | ${PAGE_ID.NOT_FOUND_PAGE}`;
this.eventMediator.notify(MEDIATOR_EVENT.CHANGE_PAGE, PAGE_ID.NOT_FOUND_PAGE);
return null;
}
document.title = `${PROJECT_TITLE} | ${pathParts.join('')}`;
this.eventMediator.notify(MEDIATOR_EVENT.CHANGE_PAGE, pathParts.join(''));
return pathParts.join('');
private changeAppTitle(path: string, hasRoute: boolean): void {
const title = `${PROJECT_TITLE} | ${hasRoute ? path : PAGE_ID.NOT_FOUND_PAGE}`;
document.title = title;
}

public navigateTo(route: string): History {
if (this.pages.has(route)) {
const pathnameApp = window.location.pathname
.split(DEFAULT_SEGMENT)
.slice(NEXT_SEGMENT, PATH_SEGMENTS_TO_KEEP + NEXT_SEGMENT)
.join(DEFAULT_SEGMENT);
const url = `${pathnameApp}/${route}`;
const titleRoute = route === '' ? PAGE_ID.MAIN_PAGE : route;
document.title = `${PROJECT_TITLE} | ${titleRoute}`;
public navigateTo(path: string): void {
const pathnameApp = window.location.pathname
.split(DEFAULT_SEGMENT)
.slice(NEXT_SEGMENT, PATH_SEGMENTS_TO_KEEP + NEXT_SEGMENT)
.join(DEFAULT_SEGMENT);
const url = `${pathnameApp}/${path}`;
history.pushState(path, '', url);

history.pushState(route, '', url);
const pathParts = url.split(DEFAULT_SEGMENT);
const hasRoute = this.routes.has(pathParts[1]);
this.changeAppTitle(pathParts[1], hasRoute);

this.eventMediator.notify(MEDIATOR_EVENT.CHANGE_PAGE, route);
if (!hasRoute) {
this.routes.get(PAGE_ID.NOT_FOUND_PAGE)?.();
return;
}
return window.history;

this.routes.get(pathParts[1])?.();
}

public setPages(pages: Map<string, Page>): Map<string, Page> {
this.pages = pages;
return this.pages;
public setRoutes(routes: Map<string, () => Page>): Map<string, () => Page> {
this.routes = routes;
return this.routes;
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/app/styles/common.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
html,
body {
display: flex;
flex-direction: column;
align-items: center;
margin: 0 auto;
width: 100%;
min-width: 320px;
Expand Down
35 changes: 16 additions & 19 deletions src/entities/Navigation/model/NavigationModel.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import type RouterModel from '@/app/Router/model/RouterModel';

import EventMediatorModel from '@/shared/EventMediator/model/EventMediatorModel.ts';
import getStore from '@/shared/Store/Store.ts';
import observeStore, { selectCurrentUser } from '@/shared/Store/observer.ts';
import MEDIATOR_EVENT from '@/shared/constants/events.ts';
import observeStore, { selectCurrentPage, selectCurrentUser } from '@/shared/Store/observer.ts';
import { PAGE_ID } from '@/shared/constants/pages.ts';

import NavigationView from '../view/NavigationView.ts';

class NavigationModel {
private eventMediator = EventMediatorModel.getInstance();

private router: RouterModel;

private view = new NavigationView();
Expand All @@ -33,13 +29,14 @@ class NavigationModel {

private init(): boolean {
this.setNavigationLinksHandlers();
this.observeCurrentUser();
this.subscribeToEventMediator();
this.switchLinksState();
this.observeState();
return true;
}

private observeCurrentUser(): boolean {
observeStore(selectCurrentUser, () => this.checkCurrentUser.bind(this));
private observeState(): boolean {
observeStore(selectCurrentUser, () => this.checkCurrentUser());
observeStore(selectCurrentPage, () => this.switchLinksState());
return true;
}

Expand All @@ -55,16 +52,16 @@ class NavigationModel {
return true;
}

private subscribeToEventMediator(): boolean {
this.eventMediator.subscribe(MEDIATOR_EVENT.CHANGE_PAGE, (route) => {
const currentRoute = route === '' ? PAGE_ID.MAIN_PAGE : route;
const navigationLinks = this.view.getNavigationLinks();
const currentLink = navigationLinks.get(String(currentRoute));
navigationLinks.forEach((link) => link.setEnabled());
this.checkCurrentUser();
currentLink?.setDisabled();
this.view.switchActiveLink(String(currentRoute));
});
private switchLinksState(): boolean {
const { currentPage } = getStore().getState();
const currentPath = currentPage === '' ? PAGE_ID.MAIN_PAGE : currentPage;
const navigationLinks = this.view.getNavigationLinks();
const currentLink = navigationLinks.get(String(currentPath));
navigationLinks.forEach((link) => link.setEnabled());
this.checkCurrentUser();
currentLink?.setDisabled();
this.view.switchActiveLink(String(currentPath));

return true;
}

Expand Down
2 changes: 1 addition & 1 deletion src/features/CountryChoice/model/CountryChoiceModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class CountryChoiceModel {

private setCountryToStore(element: HTMLDivElement | HTMLInputElement, key: string): boolean {
const currentCountryIndex = getCountryIndex(
element instanceof HTMLDivElement ? formattedText(element.textContent ?? '') || '' : formattedText(element.value),
element instanceof HTMLDivElement ? formattedText(element.textContent ?? '') : formattedText(element.value),
);

const action = key === BILLING_ADDRESS_COUNTRY.inputParams.id ? setBillingCountry : setShippingCountry;
Expand Down
5 changes: 3 additions & 2 deletions src/features/InputFieldValidator/validators/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import getStore from '@/shared/Store/Store.ts';
import COUNTRIES_LIST from '@/shared/constants/countriesList.ts';
import { USER_POSTAL_CODE } from '@/shared/constants/forms.ts';
import { ERROR_MESSAGE } from '@/shared/constants/messages.ts';
import { checkInputLanguage } from '@/shared/utils/getCountryIndex.ts';
import { maxAgeMessage, maxLengthMessage, minAgeMessage, minLengthMessage } from '@/shared/utils/messageTemplate.ts';
import { postcodeValidator } from 'postcode-validator';

Expand Down Expand Up @@ -72,11 +73,11 @@ export const checkValidAge = (value: string, validParams: InputFieldValidatorPar
export const checkValidCountry = (value: string, validParams: InputFieldValidatorParams): boolean | string => {
if (validParams.validCountry) {
if (
!Object.keys(COUNTRIES_LIST[getStore().getState().currentLanguage]).find(
!Object.keys(COUNTRIES_LIST[checkInputLanguage(value)]).find(
(countryName) => countryName.toLowerCase() === value.toLowerCase(),
)
) {
return ERROR_MESSAGE[getStore().getState().currentLanguage].INVALID_COUNTRY;
return ERROR_MESSAGE[checkInputLanguage(value)].INVALID_COUNTRY;
}
}
return true;
Expand Down
2 changes: 1 addition & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ import AppModel from '@/app/App/model/AppModel.ts';
import '@/styles.scss';

const myApp = new AppModel();
document.body.append(myApp.getHTML());
myApp.start();
41 changes: 15 additions & 26 deletions src/pages/LoginPage/model/LoginPageModel.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import type RouterModel from '@/app/Router/model/RouterModel.ts';
import type { Page } from '@/shared/types/common.ts';
import type { User } from '@/shared/types/user.ts';

import EventMediatorModel from '@/shared/EventMediator/model/EventMediatorModel.ts';
import getStore from '@/shared/Store/Store.ts';
import MEDIATOR_EVENT from '@/shared/constants/events.ts';
import { setCurrentPage } from '@/shared/Store/actions.ts';
import observeStore, { selectCurrentUser } from '@/shared/Store/observer.ts';
import { PAGE_ID, PAGE_LINK_TEXT, PAGE_LINK_TEXT_KEYS } from '@/shared/constants/pages.ts';
import observeCurrentLanguage from '@/shared/utils/observeCurrentLanguage.ts';
import LoginFormModel from '@/widgets/LoginForm/model/LoginFormModel.ts';

import LoginPageView from '../view/LoginPageView.ts';

class LoginPageModel implements Page {
private eventMediator = EventMediatorModel.getInstance();

private loginForm = new LoginFormModel();

private router: RouterModel;
Expand All @@ -25,20 +24,24 @@ class LoginPageModel implements Page {
this.init();
}

private checkAuthUser(): boolean {
if (!getStore().getState().currentUser) {
this.view.show();
this.loginForm.getFirstInputField().getView().getInput().getHTML().focus();
return false;
private checkAuthUser(): User | null {
const { currentUser } = getStore().getState();

if (currentUser) {
this.router.navigateTo(PAGE_ID.MAIN_PAGE);
return currentUser;
}
this.router.navigateTo(PAGE_ID.MAIN_PAGE);
return true;

return null;
}

private init(): boolean {
this.subscribeToEventMediator();
getStore().dispatch(setCurrentPage(PAGE_ID.LOGIN_PAGE));
this.checkAuthUser();
this.view.getAuthWrapper().append(this.loginForm.getHTML());
this.loginForm.getFirstInputField().getView().getInput().getHTML().focus();
this.setRegisterLinkHandler();
observeStore(selectCurrentUser, () => this.checkAuthUser());
return true;
}

Expand All @@ -59,20 +62,6 @@ class LoginPageModel implements Page {
toRegisterPageWrapper.append(registerLinkCopy);
}

private subscribeToEventMediator(): void {
this.eventMediator.subscribe(MEDIATOR_EVENT.CHANGE_PAGE, (route) => this.switchPageVisibility(route));
}

private switchPageVisibility(route: unknown): boolean {
if (route === PAGE_ID.LOGIN_PAGE) {
this.checkAuthUser();
} else {
this.view.hide();
return false;
}
return true;
}

public getHTML(): HTMLDivElement {
return this.view.getHTML();
}
Expand Down
14 changes: 1 addition & 13 deletions src/pages/LoginPage/view/LoginPageView.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import LinkModel from '@/shared/Link/model/LinkModel.ts';
import getStore from '@/shared/Store/Store.ts';
import { PAGE_TIMEOUT_DURATION } from '@/shared/constants/animations.ts';
import {
PAGE_ANSWER,
PAGE_ANSWER_KEYS,
Expand Down Expand Up @@ -36,6 +35,7 @@ class LoginPageView {

constructor(parent: HTMLDivElement) {
this.parent = parent;
this.parent.innerHTML = '';
this.toRegisterPageWrapper = this.createToRegisterPageWrapper();
this.loginSpan = this.createLoginSpan();
this.designElement = this.createDesignElement();
Expand Down Expand Up @@ -155,17 +155,5 @@ class LoginPageView {
public getToRegisterPageWrapper(): HTMLSpanElement {
return this.toRegisterPageWrapper;
}

public hide(): boolean {
this.page.classList.add(styles.loginPage_hidden);
return true;
}

public show(): boolean {
setTimeout(() => {
this.page.classList.remove(styles.loginPage_hidden);
}, PAGE_TIMEOUT_DURATION);
return true;
}
}
export default LoginPageView;
Loading