Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
2685532
Merge pull request #19 from kirill-zhirnov/15_product_page
Jan 17, 2022
58402db
working
Jan 18, 2022
0a96832
working
Jan 18, 2022
3075473
working
Jan 18, 2022
e137b22
working
Jan 19, 2022
9bbe740
working
Jan 20, 2022
0a222c9
aside menu and other improvements
Jan 21, 2022
0d98444
added generic menu to redux.
Jan 22, 2022
4a9b969
cart fixes
Jan 22, 2022
4aca82d
cart fixes
Jan 22, 2022
6ffb74e
linter version fix
Jan 22, 2022
cbcefa5
Merge pull request #43 from kirill-zhirnov/linters_add
Jan 22, 2022
243fb84
made common breadcrumbs component
keXXpert Jan 26, 2022
2c37d56
added variant pick animation
keXXpert Jan 26, 2022
59ba289
added custom hook
keXXpert Jan 26, 2022
0523807
added vertical menu component
keXXpert Jan 27, 2022
72af175
lint fix
keXXpert Jan 27, 2022
f5931c5
updated makeBreadCrumb interface
keXXpert Jan 27, 2022
3379ebd
improved animation
keXXpert Jan 28, 2022
ccfa067
updated v-menu classes and styling
keXXpert Jan 28, 2022
d4e0aca
updated callbacks
keXXpert Jan 28, 2022
5545c10
added filters collapse, check-buttons
keXXpert Jan 28, 2022
60bfa5e
minor style improvements
Jan 28, 2022
b08ec1f
Merge pull request #44 from kirill-zhirnov/30_breadcrumbs
Jan 28, 2022
7c0295d
Merge pull request #45 from kirill-zhirnov/35_animate_error
Jan 31, 2022
23c9ea4
Merge pull request #47 from kirill-zhirnov/36_vertical_menu
Jan 31, 2022
241aa23
added range slider, filters lazy load
keXXpert Feb 2, 2022
6cf9f5f
Merge branch 'master' into 28_filters
keXXpert Feb 2, 2022
d13c9b7
added popper
keXXpert Feb 3, 2022
2470d76
different filters improvements
keXXpert Feb 3, 2022
5920174
added horizontal menu
keXXpert Feb 4, 2022
0269378
Merge pull request #49 from kirill-zhirnov/28_filters
Feb 4, 2022
71a4e9f
the job on the checkout has just begun
Feb 4, 2022
e627642
made class component for horMenu
keXXpert Feb 4, 2022
017ed68
added animation, BEM classes
keXXpert Feb 4, 2022
fbf8524
Merge branch 'master' into 37_horizontal_menu
keXXpert Feb 4, 2022
ca82c9c
different hor menu improvements
keXXpert Feb 7, 2022
82a125f
fixed update error
keXXpert Feb 7, 2022
f112a05
intial commit
keXXpert Feb 8, 2022
7a54a9a
fixed duplicate ids
keXXpert Feb 8, 2022
70eeaed
updated horizontal menu links and styling
keXXpert Feb 8, 2022
6ee5082
added filters mobile dialog
keXXpert Feb 8, 2022
ad05c5f
lint fix
keXXpert Feb 8, 2022
bebc4ef
Merge pull request #50 from kirill-zhirnov/37_horizontal_menu
Feb 8, 2022
c3860d1
minor improvements
Feb 9, 2022
7900234
updated footer component
keXXpert Feb 9, 2022
4759c31
updated foter css class naming
keXXpert Feb 10, 2022
f5647e4
mobile filters form improvements
keXXpert Feb 10, 2022
862bb34
Merge branch 'master' into 51_filters_mobile
keXXpert Feb 10, 2022
af801d0
lint fix
keXXpert Feb 10, 2022
526d11d
initial aside menu setup
keXXpert Feb 11, 2022
8d43912
added aside menu swipe effect
keXXpert Feb 11, 2022
ab99ce8
rm console.log
Feb 11, 2022
a371996
fixed styles and naming
keXXpert Feb 11, 2022
94677bc
updated cart component
keXXpert Feb 11, 2022
ed193e5
Merge pull request #53 from kirill-zhirnov/51_filters_mobile
Feb 12, 2022
e4e6df5
Merge branch 'master' into 38_footer
keXXpert Feb 14, 2022
1ca0e09
fixed swipe speed
keXXpert Feb 14, 2022
3d68aa3
aside menu improvements
keXXpert Feb 14, 2022
e81207c
added ssr menu in cart, updated cart styling
keXXpert Feb 14, 2022
056d72d
hid sort icon on inactive filter
keXXpert Feb 14, 2022
1e79748
added category title sort, FA icons import
keXXpert Feb 14, 2022
f0ca78a
minor adjustments
Feb 15, 2022
be752a6
Merge pull request #55 from kirill-zhirnov/38_footer
Feb 15, 2022
dc49c35
minor adjustments
Feb 15, 2022
541f617
Merge pull request #60 from kirill-zhirnov/40_aside_menu
Feb 15, 2022
50eae68
Merge pull request #61 from kirill-zhirnov/32_sorting
Feb 15, 2022
ef47108
update
Feb 15, 2022
a63c318
merged with master
Feb 15, 2022
b071b15
trying to launch a packet
Feb 15, 2022
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
27 changes: 27 additions & 0 deletions .github/workflows/linters.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Linters
#on: push

on:
push:
branches-ignore: [master]
pull_request:
branches: [master]

jobs:
super-lint:
name: Lint code base
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2

- name: Run Super-Linter
uses: github/super-linter@v4
env:
DEFAULT_BRANCH: master
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# validate only typescript:
VALIDATE_TYPESCRIPT_ES: true
VALIDATE_CSS: true
CSS_FILE_NAME: .stylelintrc.json
LINTER_RULES_PATH: /
19 changes: 19 additions & 0 deletions @types/components.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export interface IBreadCrumbItem {
title: string;
isActive?: boolean;
url?: string
}

export interface IMenuItem {
title: string,
url?: string,
img?: IMenuItemImage,
isActive?: boolean,
children?: IMenuItem[]
}

export interface IMenuItemImage {
src: string,
width?: number,
height?: number
}
File renamed without changes
File renamed without changes
File renamed without changes
105 changes: 105 additions & 0 deletions components/AsideMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import clsx from 'clsx';
import {useAppDispatch, useAppSelector} from '../hooks/redux';
import {RootState} from '../redux/store';
import {disableBodyScroll, enableBodyScroll, clearAllBodyScrollLocks} from 'body-scroll-lock';
import {useEffect, useRef} from 'react';
import {setIsOpened} from '../redux/reducers/asideMenu';
import HeaderCart from './cart/HeaderCart';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faTimes} from '@fortawesome/free-solid-svg-icons';
import {IMenuItem} from '../redux/reducers/menus';
import AsideMenuList from './asideMenu/MenuList';
import {DragGesture} from '@use-gesture/vanilla';

export default function AsideMenu({menuList}: {menuList?: IMenuItem[]}) {
const rootEl = useRef(null);
const isOpened = useAppSelector((state: RootState) => state.asideMenu.isOpened);
const isRouteChanging = useAppSelector((state: RootState) => state.app.isRouteChanging);
const dispatch = useAppDispatch();
const gesture = useRef<DragGesture | null>(null);

const closeIfOpened = () => {
if (isOpened) {
dispatch(setIsOpened(false));
}
};
const onEscPressed = (e: KeyboardEvent) => {
if (e.defaultPrevented || !isOpened) {
return;
}

if (e.key !== undefined && e.key === 'Escape') {
closeIfOpened();
} else if (e.keyCode !== undefined && e.keyCode === 27) {
closeIfOpened();
}
};

const enableSwipe = () => {
const body = document.querySelector('body');

body!.style.touchAction = 'none';
gesture.current = new DragGesture(body!, ({last, direction: [dx], velocity: [vx, vy], movement}) => {
// if (last && vx > 0.3 && (Math.abs(vy) < Math.abs(vx)) && dx === 1) {
if (last && movement[0] > 100) {
closeIfOpened();
}
});
};

const disableSwipe = () => {
const body = document.querySelector('body');

if (gesture.current) gesture.current.destroy();
gesture.current = null;
body!.style.touchAction = 'auto';
};

useEffect(() => {
if (isRouteChanging) closeIfOpened();
}, [isRouteChanging]); //eslint-disable-line

useEffect(() => {
if (isOpened) {
if (rootEl.current) {
disableBodyScroll(rootEl.current);
}

enableSwipe();

window.addEventListener('resize', closeIfOpened);
window.addEventListener('keydown', onEscPressed);
} else {
if (rootEl.current) {
enableBodyScroll(rootEl.current);
}

disableSwipe();

window.removeEventListener('resize', closeIfOpened);
window.removeEventListener('keydown', onEscPressed);
}

return () => {
clearAllBodyScrollLocks();
window.removeEventListener('resize', closeIfOpened);
window.removeEventListener('keydown', onEscPressed);

disableSwipe();
};
}, [isOpened]);//eslint-disable-line

return (
<aside className={clsx('aside-menu', {'aside-menu_visible': isOpened})}
ref={rootEl}
>
<div className='aside-menu__header'>
<HeaderCart className={'cart-header cart-header_horizontal'} />
<button className='btn' onClick={closeIfOpened} >
<FontAwesomeIcon icon={faTimes} />
</button>
</div>
{menuList && <AsideMenuList menuList={menuList} />}
</aside>
);
}
53 changes: 21 additions & 32 deletions components/BreadCrumbs.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import {ICategoryFlatItem} from 'boundless-api-client/types/catalog/category';
import clsx from 'clsx';
import Link from 'next/link';
import {TQuery} from '../@types/common';
import {getCategoryUrl} from '../lib/services/urls';
import {IBreadCrumbItem} from '../@types/components';

export default function BreadCrumbs({parents, activeParams}: IBreadCrumbsProps) {
const _parents = [...parents];
export default function BreadCrumbs({items}: {items: IBreadCrumbItem[]}) {
const isEmpty = items.length === 0;

const richItemAttrs = {
itemProp: 'itemListElement',
Expand All @@ -14,41 +12,32 @@ export default function BreadCrumbs({parents, activeParams}: IBreadCrumbsProps)
};

return (
<nav className='breadcrumb-wrapper'>
<ol className='breadcrumb' itemProp='breadcrumb' itemScope itemType='http://schema.org/BreadcrumbList'>
<nav className={clsx('breadcrumb-wrapper', isEmpty && 'd-none')}>
{!isEmpty && <ol className='breadcrumb' itemProp='breadcrumb' itemScope itemType='http://schema.org/BreadcrumbList'>
<li className='breadcrumb-item' {...richItemAttrs}>
<Link href='/'>
<a itemProp='item'><span itemProp='name'>Home</span></a>
</Link>
<meta itemProp='position' content='1' />
</li>
{parents?.length > 0 && _parents.reverse().map((parent, i) => {
const isLast = parents.length === i + 1;
const isActive = isLast && !activeParams;
const title = parent.title || parent.joined_title;

return (
<li
className={clsx('breadcrumb-item', isActive && 'active')}
key={parent.category_id}
{...(isActive ? {} : richItemAttrs)}
>
{isActive
? title
: <Link href={getCategoryUrl(parent, activeParams)}>
{items.map((item, i) => (
<li
className={clsx('breadcrumb-item', item.isActive && 'active')}
key={i}
{...(item.url ? richItemAttrs : {})}
>
{item.url && !item.isActive
? <>
<Link href={item.url}>
<a itemProp='item'>
<span itemProp='name'>{title}</span>
<span itemProp='name'>{item.title}</span>
</a>
</Link>}
<meta itemProp='position' content={String(i + 2)} />
</li>);
})}
</ol>
</Link>
<meta itemProp='position' content={String(i + 2)} />
</>
: item.title}
</li>))}
</ol>}
</nav>
);
}

interface IBreadCrumbsProps {
parents: ICategoryFlatItem[];
activeParams?: TQuery;
}
75 changes: 45 additions & 30 deletions components/FilterForm.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {IFilterField, IFilterFieldRange, TCharacteristicType, TFilterFieldType} from 'boundless-api-client';
import {TQuery} from '../@types/common';
import {SyntheticEvent, useCallback, useEffect, useState} from 'react';
import {apiClient} from '../lib/services/api';
import {apiClient} from '../lib/api';
import PriceRangeField from './filterForm/PriceRange';
import _debounce from 'lodash/debounce';
import _omit from 'lodash/omit';
Expand All @@ -12,7 +12,9 @@ import TextCharacteristic from './filterForm/TextCharacteristic';
import BrandSelect from './filterForm/BrandSelect';
import Stock from './filterForm/Stock';
import _isEqualWith from 'lodash/isEqualWith';
import {filterKeys, filterProductsQuery} from '../lib/services/category';
import {filterKeys, filterProductsQuery} from '../lib/category';

const DEFAULT_DISPLAY_LIMIT = 3;

/**
* @param filterFields - might be passed manually, e.g. pass:
Expand All @@ -22,13 +24,13 @@ import {filterKeys, filterProductsQuery} from '../lib/services/category';
* @param onSubmit
* @constructor
*/
export default function FilterForm({filterFields, queryParams, categoryId, onSearch}: IFilterFormProps) {
export default function FilterForm({filterFields, queryParams, categoryId, onSearch, idsPrefix}: IFilterFormProps) {
const [hasChanged, setHasChanged] = useState<boolean>(false);
const [values, setValues] = useState<TQuery>({});
const [ranges, setRanges] = useState<IFilterFieldRange[]>([]);
const [isFetching, setIsFetching] = useState<boolean>(false);
const [preSearchResult, setPreSearchResult] = useState<null | number>(null);
const [initedForCategory, setInitedForCategory] = useState<number|null>(null);
const [initedForCategory, setInitedForCategory] = useState<number | null>(null);

const getData = () => {
const sanitizedQuery = sanitizeIncomingQuery(queryParams);
Expand Down Expand Up @@ -60,18 +62,14 @@ export default function FilterForm({filterFields, queryParams, categoryId, onSea
}).catch(console.error);
}, 600), [categoryId]);

const onChange = (key: string, value: any, characteristicId?: number) => {
let newValues: TQuery = {};
if (key === 'props') {
const props = Object.assign({}, values.props, {[characteristicId!]: value});
newValues = {...values, ...{props: props}};
} else {
newValues = {...values, ...{[key]: value}};
}
const onChange = (value: {[key: string]: any}) => {
const newValues: TQuery = 'props' in value
? {...values, props: Object.assign({}, values.props, value.props)}
: {...values, ...value};

setHasChanged(true);
setValues(newValues);
reCalcRanges(newValues);
setHasChanged(true);
};

const onSubmit = (e: SyntheticEvent) => {
Expand Down Expand Up @@ -105,54 +103,68 @@ export default function FilterForm({filterFields, queryParams, categoryId, onSea
}

return (
<form className={'filters px-1'} onSubmit={onSubmit}>
<form className={'category-filters px-1'} onSubmit={onSubmit}>
{ranges.map((filterField, i) => {
switch (filterField.type) {
case TFilterFieldType.price:
return <PriceRangeField field={filterField}
onChange={onChange}
values={values}
key={i} />;
key={i}
idsPrefix={idsPrefix}
/>;

case TFilterFieldType.brand:
return <BrandSelect field={filterField}
onChange={onChange}
values={values}
key={i} />;
displayLimit={DEFAULT_DISPLAY_LIMIT}
key={i}
idsPrefix={idsPrefix}
/>;

case TFilterFieldType.availability:
return <Stock field={filterField}
onChange={onChange}
values={values}
key={i} />;
key={i}
idsPrefix={idsPrefix}
/>;

case TFilterFieldType.characteristic: {
if (isMultiCaseType(filterField.characteristic!.type)) {
return <MultipleSelectCharacteristic
field={filterField}
onChange={onChange}
values={values}
key={i} />;
displayLimit={DEFAULT_DISPLAY_LIMIT}
key={i}
idsPrefix={idsPrefix}
/>;
} else {
return <TextCharacteristic
field={filterField}
onChange={onChange}
values={values}
key={i} />;
key={i}
idsPrefix={idsPrefix}
/>;
}
}
}
})}
<div className='btn-group' role='group'>
<button type='button'
className='btn btn-secondary'
onClick={onClear}
disabled={isFetching}
>Clear</button>
<button type='submit'
className='btn btn-primary'
disabled={!hasChanged || isFetching}
>{getSubmitLabel(hasChanged, isFetching, preSearchResult)}</button>
<div className='category-filters__actions'>
<div className='btn-group' role='group'>
<button type='button'
className='btn btn-secondary'
onClick={onClear}
disabled={isFetching}
>Clear</button>
<button type='submit'
className='btn btn-primary'
disabled={!hasChanged || isFetching}
>{getSubmitLabel(hasChanged, isFetching, preSearchResult)}</button>
</div>
</div>
</form>
);
Expand Down Expand Up @@ -285,10 +297,13 @@ interface IFilterFormProps {
queryParams: TQuery;
categoryId: number;
onSearch: (data: TQuery) => void;
idsPrefix: string;
}

export interface IFilterFieldProps {
field: IFilterFieldRange;
values: TQuery;
onChange: (key: string, value: any, characteristicId?: number) => void;
onChange: (value: {[key: string]: any}) => void;
idsPrefix: string;
displayLimit?: number;
}
Loading