├── public ├── _redirects ├── robots.txt ├── favicon.ico ├── logo192.png ├── logo512.png ├── manifest.json └── index.html ├── src ├── react-app-env.d.ts ├── components │ ├── pages │ │ ├── adminPages │ │ │ ├── ProductsPage │ │ │ │ ├── ProductsPage.module.css │ │ │ │ └── ProductsPage.tsx │ │ │ ├── SettingsPage │ │ │ │ ├── SettingsPage.module.css │ │ │ │ └── SettingsPage.tsx │ │ │ ├── AdminPage │ │ │ │ ├── AdminPage.module.css │ │ │ │ └── AdminPage.tsx │ │ │ └── OrdersPage │ │ │ │ └── OrdersPage.tsx │ │ └── showcasePages │ │ │ ├── CategoryPage │ │ │ ├── CategoryPage.module.css │ │ │ └── CategoryPage.tsx │ │ │ ├── CartPage │ │ │ ├── CartPage.module.css │ │ │ └── CartPage.tsx │ │ │ ├── ShowcasePage │ │ │ ├── ShowcasePage.module.css │ │ │ └── ShowcasePage.tsx │ │ │ ├── ProductPage │ │ │ ├── InfoBlock │ │ │ │ ├── InfoBlock.module.css │ │ │ │ └── InfoBlock.tsx │ │ │ ├── ProductPage.module.css │ │ │ └── ProductPage.tsx │ │ │ ├── CheckoutSuccessPage │ │ │ ├── CheckoutSuccessPage.module.css │ │ │ └── CheckoutSuccessPage.tsx │ │ │ ├── NotFound │ │ │ ├── NotFound.module.css │ │ │ └── NotFound.tsx │ │ │ ├── DiscountProductsPage │ │ │ └── DiscountProductsPage.tsx │ │ │ └── WishlistPage │ │ │ └── WishlistPage.tsx │ ├── UI │ │ ├── icons │ │ │ ├── LockIcon │ │ │ │ ├── LockIcon.module.css │ │ │ │ └── LockIcon.tsx │ │ │ ├── ProductIcon │ │ │ │ ├── ProductIcon.module.css │ │ │ │ └── ProductIcon.tsx │ │ │ ├── CategoryIcon │ │ │ │ ├── CategoryIcon.module.css │ │ │ │ └── CategoryIcon.tsx │ │ │ ├── OrderIcon │ │ │ │ ├── OrderIcon.module.css │ │ │ │ └── OrderIcon.tsx │ │ │ ├── MenuIcon │ │ │ │ ├── MenuIcon.module.css │ │ │ │ └── MenuIcon.tsx │ │ │ ├── ArrowLeftIcon │ │ │ │ ├── ArrowLeftIcon.module.css │ │ │ │ └── ArrowLeftIcon.tsx │ │ │ ├── FavoriteIcon │ │ │ │ ├── FavoriteIcon.module.css │ │ │ │ └── FavoriteIcon.tsx │ │ │ ├── CartIcon │ │ │ │ ├── CartIcon.module.css │ │ │ │ └── CartIcon.tsx │ │ │ ├── CloseIcon │ │ │ │ ├── CloseIcon.module.css │ │ │ │ └── CloseIcon.tsx │ │ │ ├── EditIcon │ │ │ │ ├── EditIcon.module.css │ │ │ │ └── EditIcon.tsx │ │ │ ├── TrashIcon │ │ │ │ ├── TrashIcon.module.css │ │ │ │ └── TrashIcon.tsx │ │ │ ├── AddIcon │ │ │ │ ├── AddIcon.module.css │ │ │ │ └── AddIcon.tsx │ │ │ ├── BrandsIcon │ │ │ │ └── BrandsIcon.tsx │ │ │ ├── PriceIcon │ │ │ │ └── PriceIcon.tsx │ │ │ ├── CheckIcon │ │ │ │ └── CheckIcon.tsx │ │ │ └── DeliveryIcon │ │ │ │ └── DeliveryIcon.tsx │ │ ├── Form │ │ │ ├── Form.module.css │ │ │ └── Form.tsx │ │ ├── Placeholder │ │ │ ├── Placeholder.module.css │ │ │ └── Placeholder.tsx │ │ ├── Spinner │ │ │ ├── Spinner.tsx │ │ │ └── Spinner.module.css │ │ ├── AreaLoader │ │ │ ├── AreaLoader.tsx │ │ │ └── AreaLoader.module.css │ │ ├── Loader │ │ │ ├── Loader.tsx │ │ │ └── Loader.module.css │ │ ├── Chip │ │ │ ├── Chip.tsx │ │ │ └── Chip.module.css │ │ ├── Card │ │ │ ├── Card.tsx │ │ │ └── Card.module.css │ │ ├── IconButton │ │ │ ├── IconButton.module.css │ │ │ └── IconButton.tsx │ │ ├── Toggle │ │ │ ├── Toggle.tsx │ │ │ └── Toggle.module.css │ │ ├── BaseLink │ │ │ ├── BaseLink.tsx │ │ │ └── BaseLink.module.css │ │ ├── Badge │ │ │ ├── Badge.module.css │ │ │ └── Badge.tsx │ │ ├── LoadMore │ │ │ └── LoadMore.tsx │ │ ├── Toast │ │ │ ├── Toast.tsx │ │ │ └── Toast.module.css │ │ ├── Button │ │ │ ├── Button.tsx │ │ │ └── Button.module.css │ │ ├── Tooltip │ │ │ ├── Tooltip.tsx │ │ │ └── Tooltip.module.css │ │ ├── Checkbox │ │ │ ├── Checkbox.tsx │ │ │ └── Checkbox.module.css │ │ ├── Input │ │ │ ├── Input.module.css │ │ │ └── Input.tsx │ │ ├── Textarea │ │ │ ├── Textarea.module.css │ │ │ └── Textarea.tsx │ │ └── Select │ │ │ ├── Select.module.css │ │ │ └── Select.tsx │ ├── layouts │ │ ├── showcaseLayouts │ │ │ ├── Section │ │ │ │ ├── SectionBody │ │ │ │ │ ├── SectionBody.module.css │ │ │ │ │ ├── SectionBodyGrid │ │ │ │ │ │ ├── SectionBodyGrid.module.css │ │ │ │ │ │ └── SectionBodyGrid.tsx │ │ │ │ │ └── SectionBody.tsx │ │ │ │ ├── Section.module.css │ │ │ │ ├── SectionHeader │ │ │ │ │ ├── SectionHeader.module.css │ │ │ │ │ └── SectionHeader.tsx │ │ │ │ └── Section.tsx │ │ │ ├── ShowcaseMain │ │ │ │ ├── ShowcaseMain.module.css │ │ │ │ └── ShowcaseMain.tsx │ │ │ ├── ShowcaseFooter │ │ │ │ ├── ShowcaseFooter.module.css │ │ │ │ └── ShowcaseFooter.tsx │ │ │ └── ShowcaseHeader │ │ │ │ ├── ShowcaseHeader.module.css │ │ │ │ └── ShowcaseHeader.tsx │ │ └── adminLayouts │ │ │ ├── Content │ │ │ ├── Content.module.css │ │ │ └── Content.tsx │ │ │ ├── Main │ │ │ ├── Main.tsx │ │ │ └── Main.module.css │ │ │ ├── Header │ │ │ ├── Header.tsx │ │ │ └── Header.module.css │ │ │ ├── Actions │ │ │ ├── Actions.module.css │ │ │ └── Actions.tsx │ │ │ └── Sidebar │ │ │ ├── SidebarItem │ │ │ ├── SidebarItem.tsx │ │ │ └── SidebarItem.module.css │ │ │ ├── Sidebar.module.css │ │ │ └── Sidebar.tsx │ ├── admin │ │ ├── Order │ │ │ ├── OrderCartItem │ │ │ │ ├── OrderCartItem.module.css │ │ │ │ └── OrderCartItem.tsx │ │ │ ├── Order.module.css │ │ │ └── Order.tsx │ │ ├── Brands │ │ │ ├── Brands.module.css │ │ │ └── Brands.tsx │ │ ├── SettingsForm │ │ │ ├── SettingsForm.module.css │ │ │ └── SettingsForm.tsx │ │ ├── Categories │ │ │ ├── Categories.module.css │ │ │ └── Categories.tsx │ │ ├── ProductForm │ │ │ ├── ProductFormSelect │ │ │ │ ├── ProductFormSelect.module.css │ │ │ │ └── ProductFormSelect.tsx │ │ │ └── ProductForm.module.css │ │ ├── SettingsList │ │ │ ├── SettingsList.module.css │ │ │ └── SettingsList.tsx │ │ └── ProductsList │ │ │ └── ProductsList.module.css │ └── showcase │ │ ├── CartForm │ │ ├── CartForm.module.css │ │ └── CartForm.tsx │ │ ├── Cart │ │ ├── Cart.module.css │ │ ├── CartSummary │ │ │ ├── CartSummary.module.css │ │ │ └── CartSummary.tsx │ │ ├── Cart.tsx │ │ └── CartItem │ │ │ ├── CartItem.module.css │ │ │ └── CartItem.tsx │ │ ├── Filter │ │ ├── Filter.module.css │ │ └── Filter.tsx │ │ ├── AddToCartBtn │ │ ├── AddToCartBtn.module.css │ │ └── AddToCartBtn.tsx │ │ ├── QuantityBlock │ │ ├── QuantityBlock.module.css │ │ └── QuantityBlock.tsx │ │ ├── CategoriesList │ │ ├── CategoriesList.module.css │ │ └── CategoriesList.tsx │ │ ├── ProductCardList │ │ ├── ProductCardList.module.css │ │ └── ProductCardList.tsx │ │ ├── Menu │ │ ├── Menu.tsx │ │ └── Menu.module.css │ │ └── ProductCard │ │ ├── ProductCard.tsx │ │ └── ProductCard.module.css ├── App.module.css ├── assets │ ├── aa.png │ └── logo.png ├── constants │ ├── routes.ts │ ├── common.ts │ ├── messages.ts │ └── lockedItems.ts ├── mocks │ ├── categories.json │ └── brands.json ├── utils │ ├── helpers.ts │ └── validators.ts ├── index.css ├── index.tsx ├── store │ ├── store.ts │ ├── CommonSlice.ts │ └── UserSlice.ts ├── hooks │ ├── useOutsideClick.tsx │ ├── useFilterByBrand.tsx │ └── useForm.tsx ├── logo.svg ├── types │ └── common.ts └── App.tsx ├── .gitignore ├── tsconfig.json ├── package.json └── README.md /public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/components/pages/adminPages/ProductsPage/ProductsPage.module.css: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/components/UI/icons/LockIcon/LockIcon.module.css: -------------------------------------------------------------------------------- 1 | .icon { 2 | fill: lightgray; 3 | } -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.module.css: -------------------------------------------------------------------------------- 1 | .app { 2 | box-sizing: border-box; 3 | min-height: 100vh; 4 | } 5 | 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rizametovd/e-commerce-react-app/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rizametovd/e-commerce-react-app/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rizametovd/e-commerce-react-app/HEAD/public/logo512.png -------------------------------------------------------------------------------- /src/assets/aa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rizametovd/e-commerce-react-app/HEAD/src/assets/aa.png -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rizametovd/e-commerce-react-app/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /src/components/UI/Form/Form.module.css: -------------------------------------------------------------------------------- 1 | .form { 2 | display: flex; 3 | flex-direction: column; 4 | gap: 20px; 5 | } 6 | -------------------------------------------------------------------------------- /src/components/UI/icons/ProductIcon/ProductIcon.module.css: -------------------------------------------------------------------------------- 1 | .icon { 2 | fill: inherit; 3 | transition: all 200ms ease-out; 4 | } -------------------------------------------------------------------------------- /src/components/UI/icons/CategoryIcon/CategoryIcon.module.css: -------------------------------------------------------------------------------- 1 | .icon { 2 | fill: inherit; 3 | transition: all 200ms ease-out; 4 | } 5 | -------------------------------------------------------------------------------- /src/components/UI/icons/OrderIcon/OrderIcon.module.css: -------------------------------------------------------------------------------- 1 | .icon { 2 | fill: inherit; 3 | transition: all 200ms ease-out; 4 | } 5 | 6 | -------------------------------------------------------------------------------- /src/components/UI/icons/MenuIcon/MenuIcon.module.css: -------------------------------------------------------------------------------- 1 | 2 | .icon:hover { 3 | transition: opacity 0.3s linear; 4 | opacity: 0.7; 5 | } 6 | -------------------------------------------------------------------------------- /src/components/UI/Placeholder/Placeholder.module.css: -------------------------------------------------------------------------------- 1 | .heading { 2 | font-family: 'Nunito'; 3 | color: lightgray; 4 | text-align: center; 5 | } -------------------------------------------------------------------------------- /src/components/layouts/showcaseLayouts/Section/SectionBody/SectionBody.module.css: -------------------------------------------------------------------------------- 1 | .section-body { 2 | position: relative; 3 | height: 100%; 4 | } -------------------------------------------------------------------------------- /src/components/UI/icons/ArrowLeftIcon/ArrowLeftIcon.module.css: -------------------------------------------------------------------------------- 1 | .icon { 2 | fill: white; 3 | } 4 | 5 | .icon:hover { 6 | transition: opacity 0.3s linear; 7 | opacity: 0.7; 8 | } -------------------------------------------------------------------------------- /src/components/layouts/showcaseLayouts/Section/Section.module.css: -------------------------------------------------------------------------------- 1 | .section { 2 | display: flex; 3 | flex-direction: column; 4 | min-height: 100%; 5 | gap: 30px; 6 | } -------------------------------------------------------------------------------- /src/components/layouts/showcaseLayouts/ShowcaseMain/ShowcaseMain.module.css: -------------------------------------------------------------------------------- 1 | .main { 2 | display: flex; 3 | flex-direction: column; 4 | gap: 20px; 5 | position: relative; 6 | } -------------------------------------------------------------------------------- /src/components/UI/icons/FavoriteIcon/FavoriteIcon.module.css: -------------------------------------------------------------------------------- 1 | .icon { 2 | transition: all 150ms ease-out; 3 | } 4 | 5 | .icon:hover { 6 | transition: all 150ms ease-in; 7 | opacity: 0.7; 8 | } 9 | -------------------------------------------------------------------------------- /src/components/layouts/showcaseLayouts/ShowcaseFooter/ShowcaseFooter.module.css: -------------------------------------------------------------------------------- 1 | .footer { 2 | background-color: #e8ebf0; 3 | border-radius: 4px; 4 | padding: 10px; 5 | font-size: 12px; 6 | } 7 | -------------------------------------------------------------------------------- /src/components/admin/Order/OrderCartItem/OrderCartItem.module.css: -------------------------------------------------------------------------------- 1 | .row { 2 | pointer-events: none; 3 | } 4 | 5 | @media (max-width: 768px) { 6 | .row { 7 | display: none; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/components/UI/icons/CartIcon/CartIcon.module.css: -------------------------------------------------------------------------------- 1 | .icon { 2 | transition: all 200ms ease-out; 3 | 4 | } 5 | 6 | .icon:hover { 7 | transition: all 250ms ease-in; 8 | opacity: 0.7; 9 | } -------------------------------------------------------------------------------- /src/components/UI/Spinner/Spinner.tsx: -------------------------------------------------------------------------------- 1 | import classes from './Spinner.module.css'; 2 | 3 | const Spinner: React.FC = () => { 4 | return
; 5 | }; 6 | 7 | export default Spinner; 8 | -------------------------------------------------------------------------------- /src/components/pages/showcasePages/CategoryPage/CategoryPage.module.css: -------------------------------------------------------------------------------- 1 | @media screen and (min-width: 768px) { 2 | .wrapper { 3 | display: flex; 4 | flex-direction: column; 5 | gap: 20px; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/components/UI/icons/CloseIcon/CloseIcon.module.css: -------------------------------------------------------------------------------- 1 | .icon { 2 | fill: white; 3 | transition: all 200ms ease-out; 4 | } 5 | 6 | .icon:hover { 7 | opacity: 0.7; 8 | transition: all 250ms ease-in; 9 | } 10 | -------------------------------------------------------------------------------- /src/components/UI/icons/EditIcon/EditIcon.module.css: -------------------------------------------------------------------------------- 1 | .icon { 2 | fill: lightgray; 3 | transition: all 200ms ease-out; 4 | } 5 | 6 | .icon:hover { 7 | fill: #4faa37; 8 | transition: all 250ms ease-in; 9 | } 10 | -------------------------------------------------------------------------------- /src/components/UI/icons/TrashIcon/TrashIcon.module.css: -------------------------------------------------------------------------------- 1 | .icon { 2 | transition: all 200ms ease-out; 3 | 4 | } 5 | 6 | .icon:hover { 7 | transition: all 250ms ease-in; 8 | fill: #ef2525; 9 | } 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/components/UI/icons/AddIcon/AddIcon.module.css: -------------------------------------------------------------------------------- 1 | .icon { 2 | fill: inherit; 3 | transition: all 200ms ease-out; 4 | } 5 | 6 | .icon:hover { 7 | fill: inherit; 8 | transition: all 250ms ease-in; 9 | } 10 | 11 | -------------------------------------------------------------------------------- /src/components/pages/showcasePages/CartPage/CartPage.module.css: -------------------------------------------------------------------------------- 1 | .cart-page-body { 2 | display: flex; 3 | flex-direction: column; 4 | gap: 40px; 5 | } 6 | 7 | .title { 8 | font-family: 'Nunito'; 9 | font-size: 28px; 10 | } 11 | -------------------------------------------------------------------------------- /src/constants/routes.ts: -------------------------------------------------------------------------------- 1 | export const PATHS = { 2 | showcase: '/', 3 | products: 'products', 4 | admin: '/admin/', 5 | settings: 'settings', 6 | orders: 'orders', 7 | wishlist: '/wishlist', 8 | cart: '/cart', 9 | success: 'success', 10 | }; 11 | -------------------------------------------------------------------------------- /src/components/UI/AreaLoader/AreaLoader.tsx: -------------------------------------------------------------------------------- 1 | import classes from './AreaLoader.module.css'; 2 | 3 | const AreaLoader: React.FC = () => { 4 | return
5 |
Обновляю...
6 |
7 | } 8 | 9 | export default AreaLoader; -------------------------------------------------------------------------------- /src/components/layouts/adminLayouts/Content/Content.module.css: -------------------------------------------------------------------------------- 1 | .content { 2 | display: flex; 3 | flex-direction: column; 4 | box-sizing: border-box; 5 | /* align-items: center; */ 6 | justify-items: center; 7 | gap: 60px; 8 | position: relative; 9 | height: 100%; 10 | align-items: stretch; 11 | } 12 | -------------------------------------------------------------------------------- /src/components/layouts/showcaseLayouts/ShowcaseFooter/ShowcaseFooter.tsx: -------------------------------------------------------------------------------- 1 | import classes from './ShowcaseFooter.module.css'; 2 | 3 | const ShowcaseFooter: React.FC = () => { 4 | return ; 5 | }; 6 | 7 | export default ShowcaseFooter; 8 | -------------------------------------------------------------------------------- /src/components/layouts/adminLayouts/Main/Main.tsx: -------------------------------------------------------------------------------- 1 | import classes from './Main.module.css'; 2 | 3 | interface IMainProps { 4 | children: JSX.Element; 5 | } 6 | 7 | const Main: React.FC = ({ children }) => { 8 | return
{children}
; 9 | }; 10 | 11 | export default Main; 12 | -------------------------------------------------------------------------------- /src/constants/common.ts: -------------------------------------------------------------------------------- 1 | export const GENDER = [ 2 | { 3 | id: '1', 4 | name: 'Унисекс', 5 | url: 'unisex', 6 | }, 7 | { 8 | id: '2', 9 | name: 'Женский', 10 | url: 'female', 11 | }, 12 | { 13 | id: '3', 14 | name: 'Мужской', 15 | url: 'male', 16 | }, 17 | ]; -------------------------------------------------------------------------------- /src/components/admin/Brands/Brands.module.css: -------------------------------------------------------------------------------- 1 | .category { 2 | min-height: 160px; 3 | } 4 | 5 | .header { 6 | display: flex; 7 | justify-content: space-between; 8 | padding: 10px 0 20px; 9 | border-bottom: 1px solid lightgray; 10 | } 11 | 12 | .title { 13 | font-family: 'Nunito'; 14 | font-size: 22px; 15 | margin: 0; 16 | } -------------------------------------------------------------------------------- /src/components/pages/adminPages/SettingsPage/SettingsPage.module.css: -------------------------------------------------------------------------------- 1 | .settings { 2 | display: grid; 3 | grid-template-columns: 1fr; 4 | gap: 20px; 5 | position: relative; 6 | box-sizing: border-box; 7 | } 8 | 9 | @media screen and (min-width: 768px) { 10 | .settings { 11 | grid-template-columns: 1fr 1fr; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/components/UI/Loader/Loader.tsx: -------------------------------------------------------------------------------- 1 | import classes from './Loader.module.css'; 2 | 3 | export const Loader: React.FC = () => { 4 | return ( 5 |
6 |
7 |
8 |
9 |
10 |
11 | ); 12 | }; 13 | 14 | export default Loader; 15 | -------------------------------------------------------------------------------- /src/components/admin/SettingsForm/SettingsForm.module.css: -------------------------------------------------------------------------------- 1 | .form { 2 | padding: 30px 0 10px; 3 | } 4 | 5 | .action { 6 | display: flex; 7 | gap: 20px; 8 | flex-direction: column-reverse; 9 | } 10 | 11 | @media screen and (min-width: 768px) { 12 | .action { 13 | flex-direction: row; 14 | align-self: flex-end; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/admin/Categories/Categories.module.css: -------------------------------------------------------------------------------- 1 | .category { 2 | min-height: 160px; 3 | } 4 | 5 | .header { 6 | display: flex; 7 | justify-content: space-between; 8 | padding: 10px 0 20px; 9 | border-bottom: 1px solid lightgray; 10 | } 11 | 12 | .title { 13 | font-family: 'Nunito'; 14 | font-size: 22px; 15 | margin: 0; 16 | } 17 | -------------------------------------------------------------------------------- /src/components/layouts/adminLayouts/Content/Content.tsx: -------------------------------------------------------------------------------- 1 | import classes from './Content.module.css'; 2 | 3 | interface IContentProps { 4 | children: JSX.Element; 5 | } 6 | 7 | const Content: React.FC = ({ children }) => { 8 | return
{children}
; 9 | }; 10 | 11 | export default Content; 12 | -------------------------------------------------------------------------------- /src/components/layouts/showcaseLayouts/Section/SectionHeader/SectionHeader.module.css: -------------------------------------------------------------------------------- 1 | .section-header { 2 | display: flex; 3 | flex-direction: column; 4 | gap: 20px; 5 | } 6 | 7 | .section-header-title { 8 | font-family: 'Nunito'; 9 | font-size: 38px; 10 | margin: 0; 11 | } 12 | 13 | .section-header-description { 14 | margin: 0; 15 | } 16 | -------------------------------------------------------------------------------- /src/components/showcase/CartForm/CartForm.module.css: -------------------------------------------------------------------------------- 1 | .cart-form { 2 | background-color: #ecf7ed; 3 | padding: 16px; 4 | border-radius: 4px; 5 | box-sizing: border-box; 6 | } 7 | 8 | .action { 9 | text-align: right; 10 | } 11 | 12 | @media screen and (min-width: 1024px) { 13 | .cart-form { 14 | width: calc(100% - 420px); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/UI/Chip/Chip.tsx: -------------------------------------------------------------------------------- 1 | import classes from './Chip.module.css' 2 | 3 | interface IChipProps { 4 | text: string; 5 | mode?: 'info' | 'attention' | 'highlighted' | 'plain' 6 | } 7 | 8 | const Chip: React.FC = ({text, mode='info'}) => { 9 | return {text} 10 | } 11 | 12 | export default Chip; -------------------------------------------------------------------------------- /src/components/layouts/showcaseLayouts/Section/SectionBody/SectionBodyGrid/SectionBodyGrid.module.css: -------------------------------------------------------------------------------- 1 | .grid { 2 | display: grid; 3 | gap: 20px; 4 | justify-content: center; 5 | } 6 | 7 | .block { 8 | display: block; 9 | } 10 | 11 | @media screen and (min-width: 768px) { 12 | .grid { 13 | grid-template-columns: minmax(max-content, 210px) 1fr; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/components/layouts/showcaseLayouts/ShowcaseMain/ShowcaseMain.tsx: -------------------------------------------------------------------------------- 1 | import classes from './ShowcaseMain.module.css'; 2 | 3 | interface IShowcaseMainProps { 4 | children: JSX.Element; 5 | } 6 | 7 | const ShowcaseMain: React.FC = ({ children }) => { 8 | return
{children}
; 9 | }; 10 | 11 | export default ShowcaseMain; 12 | -------------------------------------------------------------------------------- /src/components/layouts/showcaseLayouts/Section/SectionBody/SectionBody.tsx: -------------------------------------------------------------------------------- 1 | import classes from './SectionBody.module.css'; 2 | 3 | interface ISectionBodyProps { 4 | children: JSX.Element; 5 | } 6 | const SectionBody: React.FC = ({ children }) => { 7 | return
{children}
; 8 | }; 9 | 10 | export default SectionBody; 11 | -------------------------------------------------------------------------------- /src/components/showcase/Cart/Cart.module.css: -------------------------------------------------------------------------------- 1 | .cart { 2 | display: flex; 3 | flex-direction: column; 4 | box-sizing: border-box; 5 | gap: 40px; 6 | } 7 | 8 | .cart-items-wrapper { 9 | display: flex; 10 | flex-direction: column; 11 | gap: 30px; 12 | width: 100%; 13 | } 14 | 15 | @media screen and (min-width: 1024px) { 16 | .cart { 17 | flex-direction: row; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/mocks/categories.json: -------------------------------------------------------------------------------- 1 | { 2 | "-NkfipU4uWpp7PhaMZyV": { 3 | "description": "Джи́нсы — брюки из плотной хлопчатобумажной ткани с проклёпанными стыками швов на карманах.", 4 | "name": "Джинсы", 5 | "url": "jeans" 6 | }, 7 | "-NkfiwscdixHLuW87vr3": { 8 | "description": "Большой выбор мужских и женских футболок.", 9 | "name": "Футболки", 10 | "url": "tees" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/components/showcase/Filter/Filter.module.css: -------------------------------------------------------------------------------- 1 | .filter { 2 | display: flex; 3 | flex-direction: column; 4 | gap: 8px; 5 | background-color: #e8ebf0; 6 | padding: 16px; 7 | box-sizing: border-box; 8 | border-radius: 4px; 9 | align-self: start; 10 | align-items: flex-start; 11 | } 12 | 13 | .title { 14 | font-family: 'Nunito'; 15 | font-size: 18px; 16 | padding: 0 0 20px; 17 | } 18 | -------------------------------------------------------------------------------- /src/components/UI/Card/Card.tsx: -------------------------------------------------------------------------------- 1 | import classes from './Card.module.css'; 2 | 3 | interface ICardProps { 4 | children: JSX.Element, 5 | fullWidth?: boolean 6 | } 7 | 8 | const Card: React.FC = ({children, fullWidth = false}) => { 9 | return ( 10 |
{children}
11 | ) 12 | } 13 | 14 | export default Card; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /src/mocks/brands.json: -------------------------------------------------------------------------------- 1 | { 2 | "-Nkfj5SQsQ_2rLftLjs1": { 3 | "description": "Эта британская марка, основанная Моррисом Купер, обладает богатой историей, которая ведет отсчет с 1908 года.", 4 | "name": "Lee Cooper" 5 | }, 6 | "-NkfjDXFfFEkRgs_b01F": { 7 | "description": "Американская компания, известный производитель одежды (в первую очередь джинсовой) и обуви.", 8 | "name": "Levi's®" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/components/UI/Placeholder/Placeholder.tsx: -------------------------------------------------------------------------------- 1 | import classes from './Placeholder.module.css'; 2 | 3 | interface IPlaceholderProps { 4 | text: string; 5 | size?: string; 6 | } 7 | 8 | const Placeholder: React.FC = ({ text, size = '36px' }) => { 9 | return ( 10 |

11 | {text} 12 |

13 | ); 14 | }; 15 | 16 | export default Placeholder; 17 | -------------------------------------------------------------------------------- /src/components/pages/showcasePages/ShowcasePage/ShowcasePage.module.css: -------------------------------------------------------------------------------- 1 | .showcase { 2 | min-width: 320px; 3 | max-width: 1280px; 4 | box-sizing: border-box; 5 | display: grid; 6 | grid-template-rows: fit-content(230px) auto min-content; 7 | min-height: 100vh; 8 | gap: 40px; 9 | margin: 0 auto; 10 | padding: 0 20px; 11 | } 12 | 13 | @media screen and (min-width: 1280px) { 14 | .showcase { 15 | padding: 0; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/components/UI/Form/Form.tsx: -------------------------------------------------------------------------------- 1 | import classes from './Form.module.css'; 2 | 3 | interface IFormProps { 4 | onSubmit: (e: React.FormEvent) => void; 5 | children: JSX.Element; 6 | } 7 | 8 | const Form: React.FC = ({ children, onSubmit }) => { 9 | return ( 10 |
11 | {children} 12 |
13 | ); 14 | }; 15 | 16 | export default Form; 17 | -------------------------------------------------------------------------------- /src/components/pages/adminPages/AdminPage/AdminPage.module.css: -------------------------------------------------------------------------------- 1 | .admin { 2 | display: grid; 3 | grid-template-columns: 1fr; 4 | padding: 10px; 5 | gap: 20px; 6 | box-sizing: border-box; 7 | background-color: rgb(231, 235, 240); 8 | } 9 | 10 | @media (min-width: 768px) { 11 | .admin { 12 | padding: 0; 13 | gap: 20px; 14 | grid-template-columns: fit-content(240px) 1fr; 15 | grid-template-rows: 50px auto; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/components/UI/Chip/Chip.module.css: -------------------------------------------------------------------------------- 1 | .chip { 2 | font-size: 12px; 3 | border-radius: 4px; 4 | padding: 5px 10px; 5 | align-self: flex-start; 6 | } 7 | 8 | .info { 9 | background-color: #fcf7eb; 10 | } 11 | 12 | .attention { 13 | color: #fff; 14 | background-color: #f44336; 15 | } 16 | 17 | .highlighted { 18 | background-color: #ecf7ed; 19 | } 20 | 21 | .plain { 22 | border: 1px solid lightgray; 23 | background-color: #fff; 24 | } 25 | -------------------------------------------------------------------------------- /src/components/admin/ProductForm/ProductFormSelect/ProductFormSelect.module.css: -------------------------------------------------------------------------------- 1 | .product-form-select { 2 | width: 100%; 3 | display: flex; 4 | flex-direction: column; 5 | gap: 5px; 6 | min-height: 94px; 7 | } 8 | 9 | .label { 10 | font-family: 'Nunito'; 11 | font-size: 18px; 12 | font-weight: 600; 13 | line-height: 22px; 14 | color: #333333; 15 | } 16 | 17 | .highlighted { 18 | font-size: 14px; 19 | color: #ef2525; 20 | } -------------------------------------------------------------------------------- /src/components/layouts/adminLayouts/Header/Header.tsx: -------------------------------------------------------------------------------- 1 | import { PATHS } from '../../../../constants/routes'; 2 | import BaseLink from '../../../UI/BaseLink/BaseLink'; 3 | import classes from './Header.module.css'; 4 | 5 | const Header = () => { 6 | return ( 7 |
8 | 9 | Перейти на витрину 10 | 11 |
12 | ); 13 | }; 14 | 15 | export default Header; 16 | -------------------------------------------------------------------------------- /src/components/layouts/showcaseLayouts/Section/SectionBody/SectionBodyGrid/SectionBodyGrid.tsx: -------------------------------------------------------------------------------- 1 | import classes from './SectionBodyGrid.module.css'; 2 | interface ISectionBodyGridProps { 3 | children: JSX.Element; 4 | displayBlock?: boolean; 5 | } 6 | 7 | const SectionBodyGrid: React.FC = ({ children, displayBlock = false }) => { 8 | return
{children}
; 9 | }; 10 | 11 | export default SectionBodyGrid; 12 | -------------------------------------------------------------------------------- /src/components/UI/Card/Card.module.css: -------------------------------------------------------------------------------- 1 | .card { 2 | width: 100%; 3 | max-width: 960px; 4 | display: inline-block; 5 | padding: 16px; 6 | box-sizing: border-box; 7 | border: 1px solid #fbfbfb; 8 | border-radius: 4px; 9 | background-color: #fff; 10 | position: relative; 11 | animation: fade 200ms linear; 12 | } 13 | 14 | .full-width { 15 | min-width: 100%; 16 | } 17 | 18 | @keyframes fade { 19 | from { 20 | opacity: 0; 21 | } 22 | to { 23 | opacity: 1; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/components/UI/IconButton/IconButton.module.css: -------------------------------------------------------------------------------- 1 | .button { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | border: none; 6 | outline: none; 7 | cursor: pointer; 8 | background-color: transparent; 9 | padding: 0; 10 | gap: 5px; 11 | fill: lightgray; 12 | } 13 | 14 | .button:disabled { 15 | cursor: default; 16 | fill: lightgray; 17 | } 18 | 19 | .button:hover:not(:disabled) { 20 | fill: #4faa37; 21 | } 22 | 23 | .column { 24 | flex-direction: column; 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/helpers.ts: -------------------------------------------------------------------------------- 1 | export type FirebaseObj = { 2 | [key: string]: { 3 | [key: string]: string | number | { [key: string]: string | number }; 4 | }; 5 | }; 6 | 7 | export const handleObj = (obj: FirebaseObj | null): any[] => { 8 | if (!obj) { 9 | return []; 10 | } 11 | 12 | return Object.entries(obj).map(([id, fields]) => ({ id, ...fields })); 13 | }; 14 | 15 | export const wait = async (ms: number) => { 16 | return new Promise((resolve) => { 17 | setTimeout(resolve, ms); 18 | }); 19 | }; 20 | -------------------------------------------------------------------------------- /src/components/UI/Toggle/Toggle.tsx: -------------------------------------------------------------------------------- 1 | import classes from './Toggle.module.css'; 2 | 3 | interface IToggleProps { 4 | value: boolean, 5 | onChange: () => void 6 | } 7 | 8 | const Toggle: React.FC = ({value, onChange}) => { 9 | 10 | return ( 11 | 15 | ) 16 | } 17 | 18 | export default Toggle; -------------------------------------------------------------------------------- /src/components/UI/BaseLink/BaseLink.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router-dom'; 2 | import classes from './BaseLink.module.css'; 3 | 4 | interface IBaseLinkProps { 5 | children: string; 6 | to: string; 7 | button?: boolean; 8 | size: 's' | 'm'; 9 | } 10 | const BaseLink: React.FC = ({ children, to, button, size = 'm' }) => { 11 | return ( 12 | 13 | {children} 14 | 15 | ); 16 | }; 17 | 18 | export default BaseLink; 19 | -------------------------------------------------------------------------------- /src/components/layouts/adminLayouts/Header/Header.module.css: -------------------------------------------------------------------------------- 1 | .header { 2 | background: #fcf7eb; 3 | border-bottom-left-radius: 4px; 4 | border-bottom-right-radius: 4px; 5 | text-align: center; 6 | display: flex; 7 | align-items: center; 8 | justify-content: center; 9 | } 10 | 11 | @media (min-width: 768px) { 12 | .header { 13 | width: 100%; 14 | max-width: 1280px; 15 | margin: 0 auto; 16 | grid-column-start: 2; 17 | grid-column-end: 3; 18 | grid-row-start: 1; 19 | grid-row-end: 2; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Nunito:wght@700&display=swap'); 2 | @import url('https://fonts.googleapis.com/css2?family=Nunito:wght@700&family=Open+Sans:wght@400;600&display=swap'); 3 | 4 | body { 5 | margin: 0; 6 | font-family: 'Open Sans', 'Helvetica', 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 7 | 'Droid Sans', 'Helvetica Neue', sans-serif; 8 | -webkit-font-smoothing: antialiased; 9 | -moz-osx-font-smoothing: grayscale; 10 | box-sizing: border-box; 11 | font-size: 16px; 12 | } 13 | -------------------------------------------------------------------------------- /src/components/layouts/adminLayouts/Main/Main.module.css: -------------------------------------------------------------------------------- 1 | .main { 2 | display: flex; 3 | flex-direction: column; 4 | gap: 20px; 5 | position: relative; 6 | } 7 | 8 | @media (min-width: 768px) { 9 | .main { 10 | grid-column-start: 2; 11 | grid-column-end: 3; 12 | grid-row-start: 2; 13 | grid-row-end: 3; 14 | margin: 0 auto; 15 | padding-right: 20px; 16 | padding-bottom: 40px; 17 | box-sizing: border-box; 18 | width: 100%; 19 | max-width: 1280px; 20 | display: grid; 21 | grid-template-rows: 100px 1fr; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/components/UI/icons/ArrowLeftIcon/ArrowLeftIcon.tsx: -------------------------------------------------------------------------------- 1 | import classes from './ArrowLeftIcon.module.css'; 2 | 3 | interface IArrowLeftIconProps { 4 | width?: number; 5 | height?: number; 6 | } 7 | const ArrowLeftIcon: React.FC = ({ width = 24, height = 24 }) => { 8 | return ( 9 | 10 | 11 | 12 | ); 13 | }; 14 | 15 | export default ArrowLeftIcon; 16 | -------------------------------------------------------------------------------- /src/components/UI/icons/MenuIcon/MenuIcon.tsx: -------------------------------------------------------------------------------- 1 | import classes from './MenuIcon.module.css'; 2 | 3 | interface IMenuIconProps { 4 | width?: number; 5 | height?: number; 6 | fill?: string; 7 | } 8 | const MenuIcon: React.FC = ({ width = 24, height = 24, fill="#fff" }) => { 9 | return ( 10 | 11 | 12 | 13 | ); 14 | }; 15 | 16 | export default MenuIcon; 17 | -------------------------------------------------------------------------------- /src/components/admin/ProductForm/ProductForm.module.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | max-width: 960px; 3 | width: 100%; 4 | align-self: center; 5 | } 6 | 7 | .container { 8 | display: flex; 9 | flex-direction: column; 10 | justify-content: space-between; 11 | gap: 20px; 12 | } 13 | 14 | .action { 15 | display: flex; 16 | flex-direction: column-reverse; 17 | gap: 20px; 18 | } 19 | 20 | @media screen and (min-width: 768px) { 21 | .container { 22 | flex-direction: row; 23 | } 24 | 25 | .action { 26 | flex-direction: row; 27 | align-self: flex-end; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/components/layouts/adminLayouts/Actions/Actions.module.css: -------------------------------------------------------------------------------- 1 | .actions { 2 | display: flex; 3 | flex-direction: column; 4 | gap: 20px; 5 | } 6 | 7 | .title { 8 | font-family: 'Nunito'; 9 | font-size: 20px; 10 | line-height: 22px; 11 | margin: 0; 12 | text-align: center; 13 | } 14 | 15 | @media (min-width: 768px) { 16 | .actions { 17 | flex-direction: row; 18 | justify-content: space-between; 19 | align-items: center; 20 | } 21 | 22 | .title { 23 | font-size: 32px; 24 | line-height: 32px; 25 | margin: 0; 26 | text-align: left; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/components/UI/icons/CloseIcon/CloseIcon.tsx: -------------------------------------------------------------------------------- 1 | import classes from './CloseIcon.module.css'; 2 | 3 | interface ICloseIconProps { 4 | width?: number; 5 | height?: number; 6 | } 7 | const CloseIcon: React.FC = ({ width = 20, height = 20 }) => { 8 | return ( 9 | 10 | 11 | 12 | ); 13 | }; 14 | 15 | export default CloseIcon; 16 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /src/components/pages/showcasePages/ProductPage/InfoBlock/InfoBlock.module.css: -------------------------------------------------------------------------------- 1 | .info-block { 2 | display: none; 3 | gap: 20px; 4 | width: 100%; 5 | justify-content: space-between; 6 | box-sizing: border-box; 7 | background-color: #fff; 8 | padding: 16px; 9 | border-radius: 4px; 10 | } 11 | 12 | .info-block-item { 13 | display: flex; 14 | flex-direction: column; 15 | color: #222222; 16 | gap: 5px; 17 | align-items: center; 18 | font-size: 12px; 19 | } 20 | 21 | @media screen and (min-width: 1024px) { 22 | .info-block { 23 | display: flex; 24 | margin-top: auto; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import { Provider } from 'react-redux'; 4 | import { store } from './store/store'; 5 | import App from './App'; 6 | import './index.css'; 7 | import { BrowserRouter } from 'react-router-dom'; 8 | 9 | const container = document.getElementById('root')!; 10 | const root = createRoot(container); 11 | 12 | root.render( 13 | 14 | {/* */} 15 | 16 | 17 | 18 | {/* */} 19 | 20 | ); 21 | -------------------------------------------------------------------------------- /src/components/UI/icons/EditIcon/EditIcon.tsx: -------------------------------------------------------------------------------- 1 | import classes from './EditIcon.module.css'; 2 | 3 | interface IEditIconProps { 4 | width?: number; 5 | height?: number; 6 | } 7 | const EditIcon: React.FC = ({ width = 22, height = 22 }) => { 8 | return ( 9 | 10 | 11 | 12 | ); 13 | }; 14 | 15 | export default EditIcon; 16 | -------------------------------------------------------------------------------- /src/components/layouts/showcaseLayouts/Section/SectionHeader/SectionHeader.tsx: -------------------------------------------------------------------------------- 1 | import classes from './SectionHeader.module.css'; 2 | 3 | interface ISectionHeaderProps { 4 | title: string; 5 | description?: string; 6 | } 7 | 8 | const SectionHeader: React.FC = ({ title, description }) => { 9 | return ( 10 |
11 |

{title}

12 | {description &&

{description}

} 13 |
14 | ); 15 | }; 16 | 17 | export default SectionHeader; 18 | -------------------------------------------------------------------------------- /src/store/store.ts: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit'; 2 | import brandSlice from './BrandSlice'; 3 | import categoryReducer from './CategorySlice'; 4 | import commonSlice from './CommonSlice'; 5 | import productReducer from './ProductSlice'; 6 | import userSlice from './UserSlice'; 7 | 8 | export const store = configureStore({ 9 | reducer: { 10 | common: commonSlice, 11 | category: categoryReducer, 12 | product: productReducer, 13 | brand: brandSlice, 14 | user: userSlice, 15 | }, 16 | }); 17 | 18 | export type RootState = ReturnType; 19 | export type AppDispatch = typeof store.dispatch; 20 | -------------------------------------------------------------------------------- /src/components/showcase/Filter/Filter.tsx: -------------------------------------------------------------------------------- 1 | import Checkbox from '../../UI/Checkbox/Checkbox'; 2 | import classes from './Filter.module.css'; 3 | 4 | interface IFilterProps { 5 | checkboxItems: any[]; 6 | onCheck: (id: string) => void; 7 | } 8 | 9 | const Filter: React.FC = ({ checkboxItems, onCheck }) => { 10 | return ( 11 |
12 | Фильтр по брендам 13 | {checkboxItems.map((item) => ( 14 | onCheck(item.id)} /> 15 | ))} 16 |
17 | ); 18 | }; 19 | 20 | export default Filter; 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src", 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /src/components/UI/Badge/Badge.module.css: -------------------------------------------------------------------------------- 1 | .link { 2 | text-decoration: none; 3 | } 4 | 5 | .badge { 6 | display: flex; 7 | flex-direction: column; 8 | align-items: center; 9 | } 10 | .wrapper { 11 | position: relative; 12 | } 13 | 14 | .count { 15 | min-width: 18px; 16 | height: 18px; 17 | position: absolute; 18 | top: -9px; 19 | right: -8px; 20 | font-size: 10px; 21 | background-color: #f44336; 22 | color: #fff; 23 | border-radius: 50%; 24 | align-self: center; 25 | display: flex; 26 | align-items: center; 27 | justify-content: center; 28 | z-index: 2; 29 | } 30 | 31 | .text { 32 | font-size: 12px; 33 | color: #222222; 34 | } 35 | -------------------------------------------------------------------------------- /src/components/UI/LoadMore/LoadMore.tsx: -------------------------------------------------------------------------------- 1 | import Button from '../Button/Button'; 2 | 3 | interface ILoadMoreProps { 4 | count: number; 5 | itemsListLength: number; 6 | onClick: (count: number) => void; 7 | itemsLimit: number; 8 | } 9 | 10 | const LoadMore: React.FC = ({ count, itemsListLength, onClick, itemsLimit }) => { 11 | const clickHandler = () => { 12 | if (count >= itemsListLength) return; 13 | 14 | count += itemsLimit; 15 | onClick(count); 16 | }; 17 | 18 | return ( 19 | 22 | ); 23 | }; 24 | 25 | export default LoadMore; 26 | -------------------------------------------------------------------------------- /src/components/admin/SettingsList/SettingsList.module.css: -------------------------------------------------------------------------------- 1 | .list { 2 | display: flex; 3 | flex-direction: column; 4 | gap: 28px; 5 | list-style: none; 6 | padding: 30px 0 10px; 7 | margin: 0; 8 | align-self: stretch; 9 | } 10 | 11 | .list-container { 12 | display: flex; 13 | flex-direction: column; 14 | gap: 10px; 15 | } 16 | 17 | .product-list-item { 18 | display: flex; 19 | justify-content: space-between; 20 | } 21 | 22 | .action { 23 | display: flex; 24 | gap: 10px; 25 | } 26 | 27 | .product-list-item { 28 | display: flex; 29 | justify-content: space-between; 30 | } 31 | 32 | .chip-wrapper { 33 | display: flex; 34 | gap: 10px; 35 | } 36 | -------------------------------------------------------------------------------- /src/hooks/useOutsideClick.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | const useOutsideClick = ( 4 | callback: () => void 5 | ) => { 6 | const ref = useRef(null); 7 | 8 | useEffect(() => { 9 | const handleClick = (e: Event) => { 10 | if (ref.current && !ref.current.contains(e.target as Node)) { 11 | callback(); 12 | } 13 | }; 14 | 15 | document.addEventListener('click', handleClick, true); 16 | 17 | return () => { 18 | document.removeEventListener('click', handleClick, true); 19 | }; 20 | }, [ref, callback]); 21 | 22 | return ref; 23 | }; 24 | 25 | export default useOutsideClick; 26 | -------------------------------------------------------------------------------- /src/components/UI/BaseLink/BaseLink.module.css: -------------------------------------------------------------------------------- 1 | .link { 2 | text-decoration: none; 3 | color: #333333; 4 | } 5 | 6 | .button { 7 | background-color: #fc0; 8 | border-radius: 8px; 9 | } 10 | 11 | .button:hover:not(:disabled) { 12 | transition: opacity 0.3s ease-out; 13 | opacity: 0.5; 14 | } 15 | 16 | .button:focus:active { 17 | outline: 2px solid transparent; 18 | } 19 | 20 | .button:focus:not(:disabled) { 21 | transition: all 250ms linear; 22 | border: 1px solid #ffc43f; 23 | } 24 | 25 | .s { 26 | font-size: 12px; 27 | padding: 5px 10px; 28 | border-radius: 4px; 29 | } 30 | 31 | .m { 32 | font-size: 16px; 33 | padding: 10px 20px; 34 | border-radius: 8px; 35 | } -------------------------------------------------------------------------------- /src/components/UI/icons/TrashIcon/TrashIcon.tsx: -------------------------------------------------------------------------------- 1 | import classes from './TrashIcon.module.css'; 2 | 3 | interface ITrashIconProps { 4 | width?: number; 5 | height?: number; 6 | fill?: string; 7 | } 8 | const TrashIcon: React.FC = ({ width = 20, height = 20, fill = '#222222' }) => { 9 | return ( 10 | 11 | 12 | 13 | ); 14 | }; 15 | 16 | export default TrashIcon; 17 | -------------------------------------------------------------------------------- /src/components/showcase/AddToCartBtn/AddToCartBtn.module.css: -------------------------------------------------------------------------------- 1 | .add-to-cart-btn { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | position: relative; 6 | box-sizing: border-box; 7 | border-radius: 4px; 8 | background-color: #fc0; 9 | height: 42px; 10 | width: 100%; 11 | } 12 | 13 | .main-button { 14 | display: block; 15 | width: 100%; 16 | height: 100%; 17 | background-color: transparent; 18 | border: none; 19 | padding: 0; 20 | color: #222222; 21 | cursor: pointer; 22 | } 23 | 24 | .main-button-text { 25 | font-size: 16px; 26 | } 27 | 28 | @media screen and (min-width: 768px) { 29 | .add-to-cart-btn { 30 | width: 193px; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/components/UI/Spinner/Spinner.module.css: -------------------------------------------------------------------------------- 1 | .spinner { 2 | border-radius: 4px; 3 | position: absolute; 4 | display: flex; 5 | justify-content: center; 6 | align-items: center; 7 | top: 0; 8 | left: 0; 9 | right: 0; 10 | bottom: 0; 11 | opacity: 0.7; 12 | cursor: default; 13 | } 14 | 15 | .spinner::before { 16 | content: ''; 17 | width: 19px; 18 | height: 19px; 19 | border: 3px solid lightgray; 20 | border-bottom-color: green; 21 | border-radius: 50%; 22 | box-sizing: border-box; 23 | animation: rotation 1s linear infinite; 24 | } 25 | 26 | @keyframes rotation { 27 | 0% { 28 | transform: rotate(0deg); 29 | } 30 | 100% { 31 | transform: rotate(360deg); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/components/pages/adminPages/SettingsPage/SettingsPage.tsx: -------------------------------------------------------------------------------- 1 | import classes from './SettingsPage.module.css'; 2 | import Categories from '../../../admin/Categories/Categories'; 3 | import Content from '../../../layouts/adminLayouts/Content/Content'; 4 | import Actions from '../../../layouts/adminLayouts/Actions/Actions'; 5 | import Brands from '../../../admin/Brands/Brands'; 6 | 7 | const SettingsPage: React.FC = () => { 8 | 9 | return ( 10 | <> 11 | 12 | 13 | 14 |
15 | 16 | 17 |
18 |
19 | 20 | ); 21 | }; 22 | 23 | export default SettingsPage; 24 | -------------------------------------------------------------------------------- /src/components/showcase/QuantityBlock/QuantityBlock.module.css: -------------------------------------------------------------------------------- 1 | .quantity-block { 2 | display: flex; 3 | width: 100%; 4 | min-height: 42px; 5 | border-radius: 4px; 6 | justify-content: space-between; 7 | } 8 | 9 | .quantity { 10 | align-self: center; 11 | } 12 | 13 | .button { 14 | padding: 0 14px; 15 | background-color: transparent; 16 | border: none; 17 | cursor: pointer; 18 | color: #222222; 19 | transition: color 150ms linear; 20 | } 21 | 22 | .button:disabled { 23 | color: lightgray; 24 | cursor: default; 25 | } 26 | 27 | .button:hover:not(:disabled) { 28 | transition: color 150ms linear; 29 | color: #f44336; 30 | } 31 | 32 | .button-text { 33 | font-weight: 700; 34 | font-size: 20px; 35 | } 36 | -------------------------------------------------------------------------------- /src/components/UI/Toast/Toast.tsx: -------------------------------------------------------------------------------- 1 | import classes from './Toast.module.css'; 2 | 3 | interface IToastProps { 4 | message: string; 5 | type: string; 6 | onAction?: () => void; 7 | actionName?: string; 8 | } 9 | 10 | const Toast: React.FC = ({ message, onAction = () => {}, type, actionName }) => { 11 | return ( 12 | <> 13 | {message && ( 14 |
15 | {message} 16 | {actionName && ( 17 | 18 | {actionName} 19 | 20 | )} 21 |
22 | )} 23 | 24 | ); 25 | }; 26 | 27 | export default Toast; 28 | -------------------------------------------------------------------------------- /src/components/UI/Badge/Badge.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router-dom'; 2 | import classes from './Badge.module.css'; 3 | 4 | interface IBadgeProps { 5 | icon: JSX.Element; 6 | count: number; 7 | title?: string; 8 | to: string; 9 | } 10 | 11 | const Badge: React.FC = ({ icon, to, count, title='' }) => { 12 | return ( 13 | 14 |
15 |
16 | {count > 0 &&
{count}
} 17 | {icon} 18 |
19 | {title && {title}} 20 |
21 | 22 | ); 23 | }; 24 | 25 | export default Badge; 26 | -------------------------------------------------------------------------------- /src/components/UI/Button/Button.tsx: -------------------------------------------------------------------------------- 1 | import Spinner from '../Spinner/Spinner'; 2 | import classes from './Button.module.css'; 3 | 4 | interface IButtonProps { 5 | children?: string; 6 | mode: 'primary' | 'secondary'; 7 | type?: 'button' | 'submit'; 8 | isDisabled?: boolean; 9 | onClick?: () => void; 10 | isLoading?: boolean 11 | } 12 | 13 | const Button: React.FC = ({ type = 'button', children, onClick, mode, isDisabled = false, isLoading }) => { 14 | return ( 15 | 19 | ); 20 | }; 21 | 22 | export default Button; 23 | -------------------------------------------------------------------------------- /src/components/UI/Tooltip/Tooltip.tsx: -------------------------------------------------------------------------------- 1 | import classes from './Tooltip.module.css'; 2 | import { useState } from 'react'; 3 | 4 | interface ITooltipProps { 5 | icon: JSX.Element; 6 | message: string; 7 | } 8 | 9 | const Tooltip: React.FC = ({ icon, message }) => { 10 | const [isActive, setIsActive] = useState(false); 11 | 12 | const show = () => { 13 | setIsActive(true); 14 | }; 15 | 16 | const hide = () => { 17 | setIsActive(false); 18 | }; 19 | 20 | return ( 21 |
22 | {icon} 23 | {isActive && {message}} 24 |
25 | ); 26 | }; 27 | 28 | export default Tooltip; 29 | -------------------------------------------------------------------------------- /src/components/UI/icons/ProductIcon/ProductIcon.tsx: -------------------------------------------------------------------------------- 1 | import classes from './ProductIcon.module.css'; 2 | 3 | interface IProductIconProps { 4 | width?: number; 5 | height?: number; 6 | } 7 | const ProductIcon: React.FC = ({ width = 30, height = 30 }) => { 8 | return ( 9 | 10 | 11 | 12 | ); 13 | }; 14 | 15 | export default ProductIcon; 16 | -------------------------------------------------------------------------------- /src/components/pages/showcasePages/ShowcasePage/ShowcasePage.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from 'react-router-dom'; 2 | import ShowcaseFooter from '../../../layouts/showcaseLayouts/ShowcaseFooter/ShowcaseFooter'; 3 | import ShowcaseHeader from '../../../layouts/showcaseLayouts/ShowcaseHeader/ShowcaseHeader'; 4 | import ShowcaseMain from '../../../layouts/showcaseLayouts/ShowcaseMain/ShowcaseMain'; 5 | import classes from './ShowcasePage.module.css'; 6 | 7 | const ShowcasePage: React.FC = () => { 8 | return ( 9 |
10 | 11 | 12 | <> 13 | 14 | 15 | 16 | 17 |
18 | ); 19 | }; 20 | 21 | export default ShowcasePage; 22 | -------------------------------------------------------------------------------- /src/components/UI/IconButton/IconButton.tsx: -------------------------------------------------------------------------------- 1 | import classes from './IconButton.module.css'; 2 | 3 | interface IIconButtonProps { 4 | children: JSX.Element; 5 | onClick: () => void; 6 | isDisabled?: boolean; 7 | column?: boolean; 8 | type?: 'button' | 'submit'; 9 | } 10 | 11 | const IconButton: React.FC = ({ children, onClick, isDisabled, type = 'button', column}) => { 12 | const handleClick = (e: React.MouseEvent) => { 13 | e.preventDefault(); 14 | onClick(); 15 | }; 16 | 17 | return ( 18 | 21 | ); 22 | }; 23 | 24 | export default IconButton; 25 | -------------------------------------------------------------------------------- /src/components/layouts/adminLayouts/Sidebar/SidebarItem/SidebarItem.tsx: -------------------------------------------------------------------------------- 1 | import classes from './SidebarItem.module.css'; 2 | 3 | import React from 'react'; 4 | import { NavLink } from 'react-router-dom'; 5 | 6 | interface ISideBarItem { 7 | isOpen: boolean; 8 | title: string; 9 | link: string; 10 | icon: JSX.Element; 11 | } 12 | 13 | const SideBarItem: React.FC = ({ isOpen, title, icon, link }) => { 14 | return ( 15 |
  • 16 | (isActive ? `${classes.activeLink}` : `${classes.link}`)}> 17 | {icon} 18 | {title} 19 | 20 |
  • 21 | ); 22 | }; 23 | 24 | export default SideBarItem; 25 | -------------------------------------------------------------------------------- /src/components/layouts/adminLayouts/Actions/Actions.tsx: -------------------------------------------------------------------------------- 1 | import Button from '../../../UI/Button/Button'; 2 | import classes from './Actions.module.css'; 3 | 4 | interface IActionsProps { 5 | title: string; 6 | actionBtnText?: string; 7 | onAction?: () => void; 8 | isOpen?: boolean; 9 | isDisabled?: boolean; 10 | } 11 | 12 | const Actions: React.FC = ({ title, onAction, actionBtnText, isOpen, isDisabled = false }) => { 13 | return ( 14 |
    15 |

    {title}

    16 | {actionBtnText && ( 17 | 20 | )} 21 |
    22 | ); 23 | }; 24 | 25 | export default Actions; 26 | -------------------------------------------------------------------------------- /src/components/UI/icons/BrandsIcon/BrandsIcon.tsx: -------------------------------------------------------------------------------- 1 | interface IBrandsIconProps { 2 | width?: number; 3 | height?: number; 4 | filled?: boolean; 5 | fill?: string; 6 | } 7 | const BrandsIcon: React.FC = ({ width = 20, height = 20, filled = false, fill = '#222222' }) => { 8 | return ( 9 | 16 | 17 | 18 | ); 19 | }; 20 | 21 | export default BrandsIcon; 22 | -------------------------------------------------------------------------------- /src/components/showcase/CategoriesList/CategoriesList.module.css: -------------------------------------------------------------------------------- 1 | .nav { 2 | background-color: #fcf7eb; 3 | border-radius: 4px; 4 | padding: 16px; 5 | display: flex; 6 | flex-direction: column; 7 | gap: 20px; 8 | } 9 | 10 | .title { 11 | font-family: 'Nunito'; 12 | font-size: 18px; 13 | } 14 | 15 | .list { 16 | list-style: none; 17 | padding: 0; 18 | margin: 0; 19 | display: flex; 20 | flex-direction: column; 21 | gap: 10px; 22 | } 23 | 24 | .link { 25 | text-decoration: none; 26 | color: #222222; 27 | transition: color 150ms linear; 28 | } 29 | 30 | .link:hover { 31 | transition: color 150ms linear; 32 | color: red; 33 | } 34 | 35 | .active { 36 | font-weight: 700; 37 | color: red; 38 | } 39 | 40 | @media screen and (max-width: 768px) { 41 | .nav { 42 | display: none; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/components/UI/icons/CategoryIcon/CategoryIcon.tsx: -------------------------------------------------------------------------------- 1 | import classes from './CategoryIcon.module.css'; 2 | 3 | interface ICategoryIconProps { 4 | width?: number; 5 | height?: number; 6 | } 7 | const CategoryIcon: React.FC = ({ width = 30, height = 30 }) => { 8 | return ( 9 | 10 | 11 | 12 | ); 13 | }; 14 | 15 | export default CategoryIcon; 16 | -------------------------------------------------------------------------------- /src/components/UI/AreaLoader/AreaLoader.module.css: -------------------------------------------------------------------------------- 1 | .loader { 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | right: 0; 6 | bottom: 0; 7 | background-color: white; 8 | border-radius: 20px; 9 | box-sizing: border-box; 10 | display: flex; 11 | justify-content: center; 12 | align-items: center; 13 | animation: fade 100ms linear forwards; 14 | z-index: 999; 15 | } 16 | 17 | .text { 18 | font-family: 'Nunito'; 19 | font-weight: 700; 20 | font-size: 28px; 21 | padding-bottom: 8px; 22 | background: linear-gradient(#5ec343 0 0) bottom left/0% 5px no-repeat; 23 | animation: c2 2s linear infinite; 24 | } 25 | 26 | @keyframes c2 { 27 | to { 28 | background-size: 100% 3px; 29 | opacity: 1; 30 | } 31 | } 32 | 33 | @keyframes fade { 34 | from { 35 | opacity: 0; 36 | } 37 | to { 38 | opacity: 0.8; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/components/UI/Tooltip/Tooltip.module.css: -------------------------------------------------------------------------------- 1 | .tooltip { 2 | display: flex; 3 | position: relative; 4 | } 5 | 6 | .tooltip-message { 7 | position: absolute; 8 | display: flex; 9 | background-color: #ecf7ed; 10 | border: 1px solid #f2f2f2; 11 | padding: 10px 20px; 12 | border-radius: 10px; 13 | font-size: 12px; 14 | z-index: 1; 15 | left: 50%; 16 | transform: translateX(-50%); 17 | box-shadow: 0px 3px 8px rgba(0, 0, 0, 0.1); 18 | box-sizing: border-box; 19 | min-width: 450px; 20 | } 21 | 22 | .top { 23 | top: -45px; 24 | } 25 | 26 | .right { 27 | top: 50%; 28 | left: calc(100% + 10px); 29 | transform: translateX(0) translateY(-50%); 30 | } 31 | 32 | .bottom { 33 | bottom: -45px; 34 | } 35 | 36 | .left { 37 | left: auto; 38 | right: calc(100% + 10px); 39 | top: 50%; 40 | transform: translateX(0) translateY(-50%); 41 | } 42 | -------------------------------------------------------------------------------- /src/components/UI/icons/LockIcon/LockIcon.tsx: -------------------------------------------------------------------------------- 1 | import classes from './LockIcon.module.css'; 2 | 3 | interface ILockIconProps { 4 | width?: number; 5 | height?: number; 6 | } 7 | const LockIcon: React.FC = ({ width = 22, height = 22 }) => { 8 | return ( 9 | 10 | 11 | 12 | ); 13 | }; 14 | 15 | export default LockIcon; 16 | -------------------------------------------------------------------------------- /src/components/showcase/Cart/CartSummary/CartSummary.module.css: -------------------------------------------------------------------------------- 1 | .cart-summary { 2 | align-self: flex-start; 3 | display: flex; 4 | flex-direction: column; 5 | gap: 10px; 6 | background-color: #fcf7eb; 7 | padding: 16px; 8 | box-sizing: border-box; 9 | border-radius: 4px; 10 | flex-shrink: 0; 11 | width: 100%; 12 | } 13 | 14 | .cart-summary-row { 15 | display: flex; 16 | justify-content: space-between; 17 | font-size: 14px; 18 | } 19 | 20 | .heading { 21 | font-family: 'Nunito'; 22 | font-weight: 700; 23 | font-size: 26px; 24 | padding: 0; 25 | } 26 | 27 | .profit-amount { 28 | font-weight: 700; 29 | color: #f44336; 30 | } 31 | 32 | @media screen and (min-width: 768px) { 33 | .heading { 34 | font-size: 30px; 35 | } 36 | } 37 | 38 | @media screen and (min-width: 1024px) { 39 | .cart-summary { 40 | min-width: 380px; 41 | width: auto; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/components/pages/showcasePages/CheckoutSuccessPage/CheckoutSuccessPage.module.css: -------------------------------------------------------------------------------- 1 | .checkout-success { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | padding-top: 60px; 6 | gap: 20px; 7 | } 8 | 9 | .title { 10 | font-family: 'Nunito'; 11 | font-size: 38px; 12 | color: #5ec343; 13 | text-align: center; 14 | } 15 | 16 | .subtitle { 17 | font-size: 14px; 18 | text-align: center; 19 | } 20 | 21 | .text { 22 | text-align: center; 23 | } 24 | 25 | .link { 26 | display: inline-block; 27 | text-align: center; 28 | text-decoration: none; 29 | font-size: 12px; 30 | color: #222222; 31 | padding: 5px 10px; 32 | background-color: #fc0; 33 | border-radius: 4px; 34 | transition: opacity 200ms linear; 35 | } 36 | 37 | .link:hover { 38 | opacity: 0.7; 39 | transition: opacity 200ms linear; 40 | } -------------------------------------------------------------------------------- /src/components/UI/icons/AddIcon/AddIcon.tsx: -------------------------------------------------------------------------------- 1 | import classes from './AddIcon.module.css'; 2 | 3 | interface IAddIconProps { 4 | width?: number; 5 | height?: number; 6 | } 7 | const AddIcon: React.FC = ({ width = 28, height = 28 }) => { 8 | return ( 9 | 10 | 11 | 12 | ); 13 | }; 14 | 15 | export default AddIcon; 16 | -------------------------------------------------------------------------------- /src/components/UI/icons/OrderIcon/OrderIcon.tsx: -------------------------------------------------------------------------------- 1 | import classes from './OrderIcon.module.css'; 2 | 3 | interface IOrderIconProps { 4 | width?: number; 5 | height?: number; 6 | } 7 | const OrderIcon: React.FC = ({ width = 30, height = 30 }) => { 8 | return ( 9 | 10 | 11 | 12 | ); 13 | }; 14 | 15 | export default OrderIcon; 16 | -------------------------------------------------------------------------------- /src/components/UI/Checkbox/Checkbox.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { useParams } from 'react-router-dom'; 3 | import classes from './Checkbox.module.css'; 4 | 5 | interface ICheckboxProps { 6 | onCheck: () => void; 7 | label: string; 8 | } 9 | 10 | const Checkbox: React.FC = ({ onCheck, label }) => { 11 | const [isChecked, setIsChecked] = useState(false); 12 | const { url } = useParams(); 13 | 14 | useEffect(() => { 15 | if (url) { 16 | setIsChecked(false); 17 | } 18 | }, [url]); 19 | 20 | const handleChange = () => { 21 | setIsChecked((prev) => !prev); 22 | onCheck(); 23 | }; 24 | 25 | return ( 26 | 31 | ); 32 | }; 33 | 34 | export default Checkbox; 35 | -------------------------------------------------------------------------------- /src/components/UI/Loader/Loader.module.css: -------------------------------------------------------------------------------- 1 | .lds-ring { 2 | display: inline-block; 3 | position: absolute; 4 | top: 100px; 5 | left: 50%; 6 | transform: translateX(-50%); 7 | width: 60px; 8 | height: 60px; 9 | z-index: 1; 10 | } 11 | .lds-ring div { 12 | box-sizing: border-box; 13 | display: block; 14 | position: absolute; 15 | width: 64px; 16 | height: 64px; 17 | margin: 8px; 18 | border: 8px solid #5ec343; 19 | border-radius: 50%; 20 | animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; 21 | border-color: #5ec343 transparent transparent transparent; 22 | } 23 | .lds-ring div:nth-child(1) { 24 | animation-delay: -0.45s; 25 | } 26 | .lds-ring div:nth-child(2) { 27 | animation-delay: -0.3s; 28 | } 29 | .lds-ring div:nth-child(3) { 30 | animation-delay: -0.15s; 31 | } 32 | @keyframes lds-ring { 33 | 0% { 34 | transform: rotate(0deg); 35 | } 36 | 100% { 37 | transform: rotate(360deg); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/components/UI/Button/Button.module.css: -------------------------------------------------------------------------------- 1 | .button { 2 | font-family: 'Open Sans'; 3 | font-size: 15px; 4 | color: #222222; 5 | cursor: pointer; 6 | border: none; 7 | background: transparent; 8 | padding: 7px 17px; 9 | box-sizing: border-box; 10 | border-radius: 4px; 11 | position: relative; 12 | } 13 | 14 | .button:hover:not(:disabled) { 15 | transition: opacity 0.3s ease-out; 16 | opacity: 0.5; 17 | } 18 | 19 | .button:disabled::before { 20 | content: ''; 21 | display: block; 22 | position: absolute; 23 | top: 0; 24 | left: 0; 25 | bottom: 0; 26 | right: 0; 27 | width: 100%; 28 | height: 100%; 29 | transition: all 250ms linear; 30 | background-color: #f2f2f2; 31 | opacity: 0.6; 32 | cursor: default; 33 | } 34 | 35 | .primary { 36 | color: #fff; 37 | background-color: #5ec343; 38 | } 39 | 40 | .secondary { 41 | background-color: transparent; 42 | color: #333333; 43 | border: 1px solid lightgray; 44 | } 45 | -------------------------------------------------------------------------------- /src/components/UI/Toggle/Toggle.module.css: -------------------------------------------------------------------------------- 1 | .toggle { 2 | position: relative; 3 | display: inline-flex; 4 | width: 38px; 5 | height: 20px; 6 | background-color: #5ec343; 7 | border-radius: 20px; 8 | } 9 | 10 | .input { 11 | opacity: 0; 12 | width: 0; 13 | height: 0; 14 | } 15 | 16 | .slider { 17 | position: absolute; 18 | cursor: pointer; 19 | top: 0; 20 | left: 0; 21 | right: 0; 22 | bottom: 0; 23 | background-color: lightgray; 24 | border-radius: 20px; 25 | } 26 | 27 | .slider::before { 28 | position: absolute; 29 | content: ''; 30 | height: 16px; 31 | width: 16px; 32 | left: 2px; 33 | bottom: 2px; 34 | background-color: white; 35 | transition: 0.3s; 36 | border-radius: 50%; 37 | } 38 | 39 | .input:checked + .slider { 40 | background-color: #5ec343; 41 | } 42 | 43 | .input:focus + .slider { 44 | box-shadow: 0 0 1px #5ec343; 45 | } 46 | 47 | .input:checked + .slider:before { 48 | transform: translateX(18px); 49 | } 50 | -------------------------------------------------------------------------------- /src/components/UI/Input/Input.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | gap: 5px; 5 | width: 100%; 6 | height: 98px; 7 | } 8 | 9 | .label { 10 | font-family: 'Nunito'; 11 | font-size: 18px; 12 | font-weight: 600; 13 | line-height: 22px; 14 | color: #333333; 15 | } 16 | 17 | .input { 18 | font-family: 'Open Sans'; 19 | font-size: 16px; 20 | line-height: 20px; 21 | color: #222222; 22 | padding: 10px 20px; 23 | border-radius: 4px; 24 | border: 1px solid lightgray; 25 | 26 | outline: 2px solid transparent; 27 | } 28 | 29 | .input::placeholder { 30 | font-size: 14px; 31 | color: lightgray; 32 | } 33 | 34 | .input:focus { 35 | transition: all 250ms linear; 36 | background-color: #fcf7eb; 37 | border: 1px solid #ffc43f; 38 | } 39 | 40 | .error { 41 | transition: outline 0.3s ease-in; 42 | outline: 2px solid #ef2525; 43 | } 44 | 45 | .highlighted { 46 | font-size: 14px; 47 | color: #ef2525; 48 | } 49 | -------------------------------------------------------------------------------- /src/components/UI/Textarea/Textarea.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | gap: 5px; 5 | height: 194px; 6 | } 7 | 8 | .label { 9 | font-family: 'Nunito'; 10 | font-size: 18px; 11 | font-weight: 600; 12 | line-height: 22px; 13 | color: #333333; 14 | } 15 | 16 | .textarea { 17 | font-family: 'Open Sans'; 18 | font-size: 16px; 19 | line-height: 20px; 20 | color: #222222; 21 | padding: 10px 20px; 22 | border-radius: 4px; 23 | border: 1px solid lightgray; 24 | outline: 2px solid transparent; 25 | resize: none; 26 | } 27 | 28 | .textarea::placeholder { 29 | font-size: 14px; 30 | color: lightgray; 31 | } 32 | 33 | .textarea:focus { 34 | transition: all 250ms linear; 35 | background-color: #fcf7eb; 36 | border: 1px solid #ffc43f; 37 | } 38 | 39 | .error { 40 | transition: outline 0.3s ease-in; 41 | outline: 2px solid #ef2525; 42 | } 43 | 44 | .highlighted { 45 | font-size: 14px; 46 | color: #ef2525; 47 | } 48 | -------------------------------------------------------------------------------- /src/components/UI/icons/PriceIcon/PriceIcon.tsx: -------------------------------------------------------------------------------- 1 | interface IPriceIconProps { 2 | width?: number; 3 | height?: number; 4 | filled?: boolean; 5 | fill?: string; 6 | } 7 | const PriceIcon: React.FC = ({ width = 20, height = 20, filled = false, fill = '#222222' }) => { 8 | return ( 9 | 16 | 17 | 18 | ); 19 | }; 20 | 21 | export default PriceIcon; 22 | -------------------------------------------------------------------------------- /src/components/UI/icons/CheckIcon/CheckIcon.tsx: -------------------------------------------------------------------------------- 1 | interface ICheckIconProps { 2 | width?: number; 3 | height?: number; 4 | filled?: boolean; 5 | fill?: string; 6 | } 7 | const CheckIcon: React.FC = ({ width = 20, height = 20, filled = false, fill = '#222222' }) => { 8 | return ( 9 | 16 | 17 | 18 | ); 19 | }; 20 | 21 | export default CheckIcon; 22 | -------------------------------------------------------------------------------- /src/components/showcase/CategoriesList/CategoriesList.tsx: -------------------------------------------------------------------------------- 1 | import { generatePath, NavLink } from 'react-router-dom'; 2 | import { Category } from '../../../types/common'; 3 | import classes from './CategoriesList.module.css'; 4 | 5 | interface ICategoriesListProps { 6 | categories: Category[]; 7 | } 8 | 9 | const CategoriesList: React.FC = ({ categories }) => { 10 | return ( 11 | 27 | ); 28 | }; 29 | 30 | export default CategoriesList; 31 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/components/UI/Textarea/Textarea.tsx: -------------------------------------------------------------------------------- 1 | import classes from './Textarea.module.css'; 2 | 3 | interface ITextareaProps { 4 | label: string; 5 | errorText?: string; 6 | name: string; 7 | required?: boolean; 8 | placeholder: string; 9 | value: string; 10 | onChange: (e: React.ChangeEvent) => void; 11 | } 12 | 13 | const Textarea: React.FC = ({ 14 | label, 15 | errorText, 16 | name, 17 | required = false, 18 | placeholder, 19 | onChange, 20 | value, 21 | }) => { 22 | return ( 23 |
    24 | 27 |