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

fix(RSS-ECOMM-3_40): handle popstate event #231

Merged
merged 1 commit into from
May 13, 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
2 changes: 1 addition & 1 deletion src/app/App/model/AppModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class AppModel {
},
[PAGE_ID.BLOG]: async (): Promise<Page> => {
const { default: PostListModel } = await import('@/pages/Blog/PostList/model/PostListModel.ts');
return new PostListModel(this.appView.getHTML(), this.router);
return new PostListModel(this.appView.getHTML());
},
[PAGE_ID.CART_PAGE]: async (): Promise<Page> => {
const { default: CartPageModel } = await import('@/pages/CartPage/model/CartPageModel.ts');
Expand Down
47 changes: 29 additions & 18 deletions src/app/Router/model/RouterModel.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,59 @@
import type { Page } from '@/shared/types/common.ts';

import { PAGE_ID } from '@/shared/constants/pages.ts';
import { isValidPath, isValidState } from '@/shared/types/validation/paths.ts';

const DEFAULT_SEGMENT = import.meta.env.VITE_APP_DEFAULT_SEGMENT;
const NEXT_SEGMENT = import.meta.env.VITE_APP_NEXT_SEGMENT;
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 routes: Map<string, () => Promise<Page>> = new Map();

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

window.addEventListener('popstate', async (event) => {
const currentPath: unknown = event.state;

if (!isValidState(currentPath) || !isValidPath(currentPath.path)) {
window.location.pathname = PAGE_ID.DEFAULT_PAGE;
return;
}

await this.handleRequest(currentPath.path);
});
}

private changeAppTitle(path: string, hasRoute: boolean): void {
const title = `${PROJECT_TITLE} | ${hasRoute ? path : PAGE_ID.NOT_FOUND_PAGE}`;
document.title = title;
}

public async navigateTo(path: string): Promise<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}`;
private async handleRequest(path: string): Promise<void> {
const hasRoute = this.routes.has(path);
this.changeAppTitle(path === PAGE_ID.DEFAULT_PAGE ? PAGE_ID.MAIN_PAGE : path, hasRoute);

const pathParts = url.split(DEFAULT_SEGMENT);
const hasRoute = this.routes.has(pathParts[1]);
this.changeAppTitle(pathParts[1], hasRoute);
if (!hasRoute) {
await this.routes.get(PAGE_ID.NOT_FOUND_PAGE)?.();
return;
}

await this.routes.get(path)?.();
}

public async navigateTo(path: string): Promise<void> {
const hasRoute = this.routes.has(path);
this.changeAppTitle(path === PAGE_ID.DEFAULT_PAGE ? PAGE_ID.MAIN_PAGE : path, hasRoute);

if (!hasRoute) {
await this.routes.get(PAGE_ID.NOT_FOUND_PAGE)?.();
return;
}

await this.routes.get(pathParts[1])?.();
history.pushState(path, '', url);
await this.routes.get(path)?.();
history.pushState({ path }, '', path);
}

public setRoutes(routes: Map<string, () => Promise<Page>>): Map<string, () => Promise<Page>> {
Expand Down
16 changes: 8 additions & 8 deletions src/app/styles/fonts.scss
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
@font-face {
src:
local('cerapro-black'),
url('/public/fonts/cerapro-black-webfont.woff2') format('woff2'),
url('/public/fonts/cerapro-black-webfont.woff') format('woff');
url('/fonts/cerapro-black-webfont.woff2') format('woff2'),
url('/fonts/cerapro-black-webfont.woff') format('woff');
font-family: 'Cerapro';
font-weight: 900;
font-style: normal;
Expand All @@ -12,8 +12,8 @@
@font-face {
src:
local('cerapro-bold'),
url('/public/fonts/cerapro-bold-webfont.woff2') format('woff2'),
url('/public/fonts/cerapro-bold-webfont.woff') format('woff');
url('/fonts/cerapro-bold-webfont.woff2') format('woff2'),
url('/fonts/cerapro-bold-webfont.woff') format('woff');
font-family: 'Cerapro';
font-weight: 700;
font-style: normal;
Expand All @@ -23,8 +23,8 @@
@font-face {
src:
local('cerapro-medium'),
url('/public//fonts/cerapro-medium-webfont.woff2') format('woff2'),
url('/public/fonts/cerapro-medium-webfont.woff') format('woff');
url('/fonts/cerapro-medium-webfont.woff2') format('woff2'),
url('/fonts/cerapro-medium-webfont.woff') format('woff');
font-family: 'Cerapro';
font-weight: 500;
font-style: normal;
Expand All @@ -34,8 +34,8 @@
@font-face {
src:
local('cerapro-regular'),
url('/public/fonts/cerapro-regular-webfont.woff2') format('woff2'),
url('/public/fonts/cerapro-regular-webfont.woff') format('woff');
url('/fonts/cerapro-regular-webfont.woff2') format('woff2'),
url('/fonts/cerapro-regular-webfont.woff') format('woff');
font-family: 'Cerapro';
font-weight: 400;
font-style: normal;
Expand Down
2 changes: 1 addition & 1 deletion src/entities/Navigation/model/NavigationModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class NavigationModel {

private switchLinksState(): boolean {
const { currentPage } = getStore().getState();
const currentPath = currentPage === '' ? PAGE_ID.MAIN_PAGE : currentPage;
const currentPath = currentPage === PAGE_ID.DEFAULT_PAGE ? PAGE_ID.MAIN_PAGE : currentPage;
const navigationLinks = this.view.getNavigationLinks();
const currentLink = navigationLinks.get(String(currentPath));
navigationLinks.forEach((link) => link.setEnabled());
Expand Down
13 changes: 3 additions & 10 deletions src/pages/Blog/Post/view/PostView.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import type RouterModel from '@/app/Router/model/RouterModel';
import type { Post } from '@/shared/constants/blog';

import getStore from '@/shared/Store/Store.ts';
import observeStore, { selectCurrentLanguage } from '@/shared/Store/observer.ts';
import { PAGE_ID } from '@/shared/constants/pages.ts';
import createBaseElement from '@/shared/utils/createBaseElement.ts';

import styles from './post.module.scss';
Expand All @@ -17,16 +15,13 @@ export default class PostView {

private post: Post;

private router: RouterModel;

constructor(post: Post, postClickCallback: (post: PostView) => void, router: RouterModel) {
constructor(post: Post, postClickCallback: (post: PostView) => void) {
this.post = post;
this.router = router;
this.callback = postClickCallback;
this.card = this.createCardHTML();
this.blogPost = this.createPostHtml();

this.card.addEventListener('click', (event) => this.onPostClick(event));
this.card.addEventListener('click', () => this.onPostClick());
observeStore(selectCurrentLanguage, () => this.updateLanguage());
}

Expand Down Expand Up @@ -61,9 +56,7 @@ export default class PostView {
return content;
}

private async onPostClick(event: Event): Promise<void> {
event.preventDefault();
await this.router.navigateTo(`${PAGE_ID.BLOG}/${this.post.id}`);
private onPostClick(): void {
this.callback(this);
}

Expand Down
9 changes: 2 additions & 7 deletions src/pages/Blog/PostList/model/PostListModel.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type RouterModel from '@/app/Router/model/RouterModel.ts';
import type { Post } from '@/shared/constants/blog.ts';
import type { Page } from '@/shared/types/common.ts';

Expand All @@ -20,15 +19,12 @@ export default class PostListModel implements Page {

private posts: PostView[];

private router: RouterModel;

private view: BlogPageView;

constructor(parent: HTMLDivElement, router: RouterModel) {
constructor(parent: HTMLDivElement) {
const newPost: Post[] = postsData;
this.parent = parent;
this.router = router;
this.posts = newPost.map((post) => new PostView(post, this.postClickHandler, this.router));
this.posts = newPost.map((post) => new PostView(post, this.postClickHandler));
this.view = new BlogPageView(parent, this.posts);
this.render();
this.init();
Expand All @@ -37,7 +33,6 @@ export default class PostListModel implements Page {
private init(): boolean {
getStore().dispatch(setCurrentPage(PAGE_ID.BLOG));
this.observeStoreLanguage();
window.addEventListener('popstate', () => this.render());
return true;
}

Expand Down
9 changes: 2 additions & 7 deletions src/pages/Blog/PostWidget/model/PostWidgetModel.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import type RouterModel from '@/app/Router/model/RouterModel.ts';

import PostView from '@/pages/Blog/Post/view/PostView.ts';
import observeStore, { selectCurrentLanguage } from '@/shared/Store/observer.ts';

Expand All @@ -16,15 +14,12 @@ export default class PostWidgetModel {

private posts: PostView[];

private router: RouterModel;

private view: PostWidgetView;

constructor(parent: HTMLDivElement, router: RouterModel) {
constructor(parent: HTMLDivElement) {
const shuffledPosts = [...postsData].sort(() => HALF_RANDOM - Math.random());
const newPost = shuffledPosts.slice(0, CART_COUNT);
this.router = router;
this.posts = newPost.map((post) => new PostView(post, this.postClickHandler, this.router));
this.posts = newPost.map((post) => new PostView(post, this.postClickHandler));
this.view = new PostWidgetView(parent, this.posts);
this.init();
}
Expand Down
11 changes: 1 addition & 10 deletions src/pages/MainPage/model/MainPageModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,13 @@ class MainPageModel implements Page {
this.parent = parent;
this.view = new MainPageView(this.parent);
this.navigation = new NavigationModel(this.router);
this.blogWidget = new PostWidgetModel(this.view.getHTML(), router);
this.blogWidget = new PostWidgetModel(this.view.getHTML());
this.init();
}

private init(): void {
this.getHTML().append(this.navigation.getHTML(), this.blogWidget.getHTML());
getStore().dispatch(setCurrentPage(PAGE_ID.MAIN_PAGE));
window.addEventListener('popstate', () => this.onHistoryChange());
}

private async onHistoryChange(): Promise<void> {
const path = window.location.pathname;

if (path === '/main') {
await this.router.navigateTo(PAGE_ID.MAIN_PAGE);
}
}

public getHTML(): HTMLDivElement {
Expand Down
1 change: 1 addition & 0 deletions src/pages/NotFoundPage/view/NotFoundPageView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class NotFoundPageView {

constructor(parent: HTMLDivElement) {
this.parent = parent;
this.parent.innerHTML = '';
this.logo = this.createPageLogo();
this.title = this.createPageTitle();
this.description = this.createPageDescription();
Expand Down
4 changes: 2 additions & 2 deletions src/shared/Store/test.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ vi.mock('./Store.ts', async (importOriginal) => {
billingCountry: '',
categories: [],
currentLanguage: 'en',
currentPage: '',
currentPage: '/',
currentUser: null,
isAppThemeLight: true,
isUserLoggedIn: false,
Expand Down Expand Up @@ -169,7 +169,7 @@ describe('rootReducer', () => {
billingCountry: '',
categories: [],
currentLanguage: 'en',
currentPage: '',
currentPage: '/',
currentUser: null,
isAppThemeLight: true,
isUserLoggedIn: false,
Expand Down
4 changes: 3 additions & 1 deletion src/shared/constants/initialState.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import type { State } from '../Store/reducer';

import { PAGE_ID } from './pages.ts';

const initialState: State = {
billingCountry: '',
categories: [],
currentLanguage: 'en',
currentPage: '',
currentPage: PAGE_ID.DEFAULT_PAGE,
currentUser: null,
isAppThemeLight: true,
isUserLoggedIn: false,
Expand Down
2 changes: 1 addition & 1 deletion src/shared/constants/pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export const PAGE_ID = {
BLOG: 'blog',
CART_PAGE: 'cart',
CATALOG_PAGE: 'catalog',
DEFAULT_PAGE: '',
DEFAULT_PAGE: '/',
ITEM_PAGE: 'item',
LOGIN_PAGE: 'login',
MAIN_PAGE: 'main',
Expand Down
20 changes: 20 additions & 0 deletions src/shared/types/validation/paths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { PageIdType } from '@/shared/constants/pages';

import { PAGE_ID } from '@/shared/constants/pages.ts';

interface PopStateEventState {
path: string;
}

export const isValidPath = (value: unknown): value is PageIdType =>
Object.values(PAGE_ID).findIndex((route) => route === value) !== -1;

export const isValidState = (state: unknown): state is PopStateEventState => {
if (!state) {
return false;
}
if (typeof state === 'object' && 'path' in state && typeof state.path === 'string') {
return true;
}
return false;
};
2 changes: 1 addition & 1 deletion src/widgets/Catalog/model/CatalogModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class CatalogModel {
filter.push(addFilter(FilterFields.SIZE, size));
}

return { filter, limit: 100, sort: { direction: 'desc', field: 'name', locale: 'en' } };
return { filter, limit: 100, sort: { direction: 'asc', field: 'price' } };
}

private init(): void {
Expand Down
Loading