diff --git a/package-lock.json b/package-lock.json index da58908..58f4757 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,8 @@ "web-vitals": "^2.1.4" }, "devDependencies": { + "@types/react-redux": "^7.1.25", + "@types/redux-devtools-extension": "^2.13.2", "gh-pages": "^4.0.0" } }, @@ -4242,6 +4244,28 @@ "@types/react": "*" } }, + "node_modules/@types/react-redux": { + "version": "7.1.25", + "resolved": "http://npm.prakticum-team.ru/@types/react-redux/-/react-redux-7.1.25.tgz", + "integrity": "sha512-bAGh4e+w5D8dajd6InASVIyCo4pZLJ66oLb80F9OBLO1gKESbZcRCJpTT6uLXX+HAB57zw1WTdwJdAsewuTweg==", + "dev": true, + "dependencies": { + "@types/hoist-non-react-statics": "^3.3.0", + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0", + "redux": "^4.0.0" + } + }, + "node_modules/@types/redux-devtools-extension": { + "version": "2.13.2", + "resolved": "http://npm.prakticum-team.ru/@types/redux-devtools-extension/-/redux-devtools-extension-2.13.2.tgz", + "integrity": "sha512-pN9V4wx+IW12Rf0FZl6nkvAsa+Uqa6C/mOeYai8cT3RDDVsOY+DoFt58ZeBrSLMXMgPgI4n6MDRrkET+DvkI1g==", + "deprecated": "This is a stub types definition for redux-devtools-extension (https://github.com/zalmoxisus/redux-devtools-extension). redux-devtools-extension provides its own type definitions, so you don't need @types/redux-devtools-extension installed!", + "dev": true, + "dependencies": { + "redux-devtools-extension": "*" + } + }, "node_modules/@types/resolve": { "version": "1.17.1", "resolved": "http://npm.prakticum-team.ru/@types%2fresolve/-/resolve-1.17.1.tgz", @@ -15881,6 +15905,16 @@ "@babel/runtime": "^7.9.2" } }, + "node_modules/redux-devtools-extension": { + "version": "2.13.9", + "resolved": "http://npm.prakticum-team.ru/redux-devtools-extension/-/redux-devtools-extension-2.13.9.tgz", + "integrity": "sha512-cNJ8Q/EtjhQaZ71c8I9+BPySIBVEKssbPpskBfsXqb8HJ002A3KRVHfeRzwRo6mGPqsm7XuHTqNSNeS1Khig0A==", + "deprecated": "Package moved to @redux-devtools/extension.", + "dev": true, + "peerDependencies": { + "redux": "^3.1.0 || ^4.0.0" + } + }, "node_modules/redux-thunk": { "version": "2.4.2", "resolved": "http://npm.prakticum-team.ru/redux-thunk/-/redux-thunk-2.4.2.tgz", @@ -21738,6 +21772,27 @@ "@types/react": "*" } }, + "@types/react-redux": { + "version": "7.1.25", + "resolved": "http://npm.prakticum-team.ru/@types/react-redux/-/react-redux-7.1.25.tgz", + "integrity": "sha512-bAGh4e+w5D8dajd6InASVIyCo4pZLJ66oLb80F9OBLO1gKESbZcRCJpTT6uLXX+HAB57zw1WTdwJdAsewuTweg==", + "dev": true, + "requires": { + "@types/hoist-non-react-statics": "^3.3.0", + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0", + "redux": "^4.0.0" + } + }, + "@types/redux-devtools-extension": { + "version": "2.13.2", + "resolved": "http://npm.prakticum-team.ru/@types/redux-devtools-extension/-/redux-devtools-extension-2.13.2.tgz", + "integrity": "sha512-pN9V4wx+IW12Rf0FZl6nkvAsa+Uqa6C/mOeYai8cT3RDDVsOY+DoFt58ZeBrSLMXMgPgI4n6MDRrkET+DvkI1g==", + "dev": true, + "requires": { + "redux-devtools-extension": "*" + } + }, "@types/resolve": { "version": "1.17.1", "resolved": "http://npm.prakticum-team.ru/@types%2fresolve/-/resolve-1.17.1.tgz", @@ -29294,6 +29349,13 @@ "@babel/runtime": "^7.9.2" } }, + "redux-devtools-extension": { + "version": "2.13.9", + "resolved": "http://npm.prakticum-team.ru/redux-devtools-extension/-/redux-devtools-extension-2.13.9.tgz", + "integrity": "sha512-cNJ8Q/EtjhQaZ71c8I9+BPySIBVEKssbPpskBfsXqb8HJ002A3KRVHfeRzwRo6mGPqsm7XuHTqNSNeS1Khig0A==", + "dev": true, + "requires": {} + }, "redux-thunk": { "version": "2.4.2", "resolved": "http://npm.prakticum-team.ru/redux-thunk/-/redux-thunk-2.4.2.tgz", diff --git a/package.json b/package.json index d2c71b0..1788526 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,8 @@ ] }, "devDependencies": { + "@types/react-redux": "^7.1.25", + "@types/redux-devtools-extension": "^2.13.2", "gh-pages": "^4.0.0" } } diff --git a/src/components/App/App.js b/src/components/App/App.tsx similarity index 92% rename from src/components/App/App.js rename to src/components/App/App.tsx index c20d459..70024f2 100644 --- a/src/components/App/App.js +++ b/src/components/App/App.tsx @@ -1,8 +1,8 @@ import AppStyles from './App.module.css'; -import React, { useEffect } from 'react'; +import { useEffect, FC } from 'react'; import { Routes, Route, useLocation, useNavigate } from 'react-router-dom'; -import { useSelector, useDispatch } from 'react-redux'; +import { useSelector, useDispatch } from '../../services/hooks'; import Main from '../Main/Main'; import AppHeader from '../AppHeader/AppHeader'; @@ -23,18 +23,18 @@ import IngredientDetails from '../IngredientDetails/IngredientDetails'; import OrderDetails from '../OrderDetails/OrderDetails'; import { getIngredients } from '../../services/actions/ingredients'; import { getUser } from '../../services/actions/user'; +import { TFeedData } from '../../services/types/order'; -function App() { +const App: FC = () => { const dispatch = useDispatch(); - const history = useNavigate(); - const location = useLocation(); const modalBackground = location.state?.modalBackground; const { ingredientsByType } = useSelector(state => state.menu); - const { orders } = useSelector(state => state.ws.feed); - + + const { orders } = useSelector(({ws}) => (ws.feed as TFeedData)); + useEffect(() => { dispatch(getIngredients()); dispatch(getUser()); @@ -45,7 +45,7 @@ function App() {
- } /> + } /> } /> } /> } /> diff --git a/src/components/AppHeader/AppHeader.js b/src/components/AppHeader/AppHeader.tsx similarity index 95% rename from src/components/AppHeader/AppHeader.js rename to src/components/AppHeader/AppHeader.tsx index 4dd0f22..9e2a7ed 100644 --- a/src/components/AppHeader/AppHeader.js +++ b/src/components/AppHeader/AppHeader.tsx @@ -1,10 +1,9 @@ -// import React from 'react'; +import { FC } from 'react'; import { BurgerIcon, ListIcon, Logo, ProfileIcon } from '@ya.praktikum/react-developer-burger-ui-components'; import { NavLink } from 'react-router-dom'; import AppHeaderStyles from './AppHeader.module.css'; -function AppHeader() { - +const AppHeader: FC = () => { const active = AppHeaderStyles.link + ' ' + AppHeaderStyles.active + ' pl-5 pt-4 pr-5 pb-4 text text_type_main-default'; const link = AppHeaderStyles.link + ' pl-5 pt-4 pr-5 pb-4 text text_type_main-default text_color_inactive'; const activeAccountLink = AppHeaderStyles.link + ' ' + AppHeaderStyles.active + ' ' + AppHeaderStyles.account + ' pl-5 pt-4 pr-5 pb-4 text text_type_main-default'; @@ -41,4 +40,4 @@ function AppHeader() { ); } -export default AppHeader; \ No newline at end of file +export default AppHeader; diff --git a/src/components/BurgerConstructor/BurgerConstructor.js b/src/components/BurgerConstructor/BurgerConstructor.tsx similarity index 83% rename from src/components/BurgerConstructor/BurgerConstructor.js rename to src/components/BurgerConstructor/BurgerConstructor.tsx index a8f1c10..f0df5cc 100644 --- a/src/components/BurgerConstructor/BurgerConstructor.js +++ b/src/components/BurgerConstructor/BurgerConstructor.tsx @@ -1,9 +1,9 @@ +import { FC } from 'react'; import BurgerConstructorStyles from './BurgerConstructor.module.css'; -import React, { useMemo, useRef } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import { useDispatch, useSelector } from '../../services/hooks'; import PropTypes from 'prop-types'; -import { useDrop } from 'react-dnd'; +import { useDrop, DropTargetMonitor } from 'react-dnd'; import { ConstructorElement, Button } from '@ya.praktikum/react-developer-burger-ui-components'; import TotalPrice from '../TotalPrice/TotalPrice'; @@ -12,14 +12,18 @@ import Ingredient from '../Ingredient/Ingredient'; import { sendOrder } from '../../services/actions/ingredients'; import { ADD_BUN, ADD_INGREDIENT } from '../../utils/constants'; import { useNavigate } from 'react-router-dom'; +import { TIngredientList } from '../../services/types/ingredients'; +type TBurgerConstructor = { + openOrderDetailsModal: () => void; +}; -function BurgerConstructor({ openOrderDetailsModal }) { +const BurgerConstructor: FC = ({ openOrderDetailsModal }) => { const dispatch = useDispatch(); const navigate = useNavigate(); - const {addedIngredients, ingredientsById} = useSelector(state => state.menu); - const {isAuthSuccess} = useSelector(state => state.user); + const { addedIngredients, ingredientsById } = useSelector(state => state.menu); + const { isAuthSuccess } = useSelector(state => state.user); const ingredientIds = () => { let res = addedIngredients.bun ? [addedIngredients.bun._id] : []; @@ -44,13 +48,12 @@ function BurgerConstructor({ openOrderDetailsModal }) { } } - const [{isHover}, dropTarget] = useDrop({ + const [{ isHover }, dropTarget] = useDrop({ accept: 'ingredient', - item: {}, - collect: monitor => ({ + collect: (monitor: DropTargetMonitor) => ({ isHover: monitor.isOver() }), - drop(item) { + drop(item: TIngredientList) { const type = ingredientsById[item.id].type === 'bun' ? ADD_BUN : ADD_INGREDIENT; dispatch({type: type, id: item.id}) }, diff --git a/src/components/BurgerIngredients/BurgerIngredients.js b/src/components/BurgerIngredients/BurgerIngredients.tsx similarity index 76% rename from src/components/BurgerIngredients/BurgerIngredients.js rename to src/components/BurgerIngredients/BurgerIngredients.tsx index 192d380..b6db730 100644 --- a/src/components/BurgerIngredients/BurgerIngredients.js +++ b/src/components/BurgerIngredients/BurgerIngredients.tsx @@ -1,6 +1,7 @@ +import { FC, Ref, MouseEvent } from 'react'; import BurgerIngredientsStyles from './BurgerIngredients.module.css'; -import { useDispatch, useSelector } from 'react-redux'; +import { useDispatch, useSelector } from '../../services/hooks'; import PropTypes from 'prop-types'; import { useInView } from 'react-intersection-observer'; @@ -10,19 +11,24 @@ import IngredientCategory from '../IngredientCategory/IngredientCategory'; import { SET_ACTIVE_TAB } from '../../utils/constants'; +type TCategoriesTitles = { + bun: string; + main: string; + sauce: string; +} -function BurgerIngredients({ openIngredientModal }) { +const BurgerIngredients: FC<{ openIngredientModal: (e: MouseEvent) => void }> = ({ openIngredientModal }) => { const dispatch = useDispatch(); const { ingredientsByType, activeTab } = useSelector(state => state.menu); - const categoriesTitles = { - 'bun': 'Булки', - 'main': 'Начинки', - 'sauce': 'Соусы' + const categoriesTitles: TCategoriesTitles = { + bun: 'Булки', + main: 'Начинки', + sauce: 'Соусы' } - const [bunsRef, inViewBuns] = useInView({ + const [bunRef, inViewBun] = useInView({ threshold: 0.3 }); @@ -33,13 +39,13 @@ function BurgerIngredients({ openIngredientModal }) { threshold: 0.3 }); - const categoriesOrder = [ - {name: 'bun', ref: bunsRef, inView: inViewBuns}, + const categoriesOrder: Array<{name: keyof TCategoriesTitles; ref: Ref; inView: boolean}> = [ + {name: 'bun', ref: bunRef, inView: inViewBun}, {name: 'main', ref: mainsRef, inView: inViewFilling}, {name: 'sauce', ref: saucesRef, inView: inViewSauces} ]; - const setActiveTab = (name) => { + const setActiveTab = (name: string) => { dispatch({type: SET_ACTIVE_TAB, name}) } diff --git a/src/components/ErrorBoundary/ErrorBoundary.js b/src/components/ErrorBoundary/ErrorBoundary.tsx similarity index 59% rename from src/components/ErrorBoundary/ErrorBoundary.js rename to src/components/ErrorBoundary/ErrorBoundary.tsx index 60e8a07..2eba67f 100644 --- a/src/components/ErrorBoundary/ErrorBoundary.js +++ b/src/components/ErrorBoundary/ErrorBoundary.tsx @@ -1,16 +1,24 @@ -import React from "react"; +import React, { ErrorInfo, ReactNode } from "react"; -class ErrorBoundary extends React.Component { - constructor(props) { +type TErrorProps = { + children?: ReactNode; +} + +type TErrorState = { + hasError: boolean; +}; + +class ErrorBoundary extends React.Component { + constructor(props: TErrorProps) { super(props); this.state = { hasError: false }; } - static getDerivedStateFromError(error) { + static getDerivedStateFromError() { return { hasError: true }; } - componentDidCatch(error, info) { + componentDidCatch(error: Error, info: ErrorInfo) { console.log("Произошла ошибка.", error, info); } @@ -30,4 +38,4 @@ class ErrorBoundary extends React.Component { } } -export default ErrorBoundary; \ No newline at end of file +export default ErrorBoundary; diff --git a/src/components/Ingredient/Ingredient.js b/src/components/Ingredient/Ingredient.tsx similarity index 65% rename from src/components/Ingredient/Ingredient.js rename to src/components/Ingredient/Ingredient.tsx index 36556cc..d8d00fa 100644 --- a/src/components/Ingredient/Ingredient.js +++ b/src/components/Ingredient/Ingredient.tsx @@ -1,26 +1,26 @@ import IngredientStyles from './Ingredient.module.css'; -import React, { useRef } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import PropTypes from 'prop-types'; -import { useDrag, useDrop } from 'react-dnd'; +import { useRef, FC, MouseEvent } from 'react'; +import { useDispatch, useSelector } from '../../services/hooks'; +import { useDrag, useDrop, DropTargetMonitor } from 'react-dnd'; import { DragIcon, ConstructorElement } from '@ya.praktikum/react-developer-burger-ui-components'; import { CHANGE_INGREDIENT_ORDER, DELETE_INGREDIENT } from '../../utils/constants'; +import { TIngredientList } from '../../services/types/ingredients'; - -function Ingredient({id, index}) { +const Ingredient: FC = ({id, index}) => { const dispatch = useDispatch(); - const ref = useRef(null); + const ref = useRef(null); - const {ingredientsById} = useSelector(state => state.menu); + const { ingredientsById } = useSelector(state => state.menu); - const moveItem = (prevId, newId) => { - dispatch({type: CHANGE_INGREDIENT_ORDER, prevId, newId}); + const moveItem = (prevIndex: number, newIndex: number) => { + dispatch({type: CHANGE_INGREDIENT_ORDER, prevIndex, newIndex}); } - const handleDeleteIngredient = (e) => { - const targetElement = e.target.parentNode.parentNode; + const handleDeleteIngredient = (e: MouseEvent) => { + const targetElement = (e.target as HTMLLIElement).parentNode?.parentNode as HTMLElement; + if (targetElement.classList.contains('constructor-element__action')) { dispatch({type: DELETE_INGREDIENT, index: e.currentTarget.id}); } @@ -29,7 +29,7 @@ function Ingredient({id, index}) { const [, dropRef] = useDrop({ accept: 'sortIngregient', - hover(item, monitor) { + hover(item: TIngredientList, monitor: DropTargetMonitor) { if (!ref.current) { return } @@ -42,6 +42,7 @@ function Ingredient({id, index}) { const hoverBoundingRect = ref.current?.getBoundingClientRect() const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; const clientOffset = monitor.getClientOffset(); + if (!clientOffset) return; const hoverClientY = clientOffset.y - hoverBoundingRect.top; if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) { @@ -71,7 +72,7 @@ function Ingredient({id, index}) { dragRef(dropRef(ref)); return ( -
  • +
  • void; +} + +const IngredientCard: FC = ({ data, count, openIngredientModal }) => { const location = useLocation(); const [{ opacity }, ref] = useDrag({ @@ -40,10 +46,4 @@ function IngredientCard({ data, count, openIngredientModal }) { ); } -IngredientCard.propTypes = { - data: IngredientsPropTypes.isRequired, - count: PropTypes.number, - openIngredientModal: PropTypes.func.isRequired -} - export default IngredientCard; diff --git a/src/components/IngredientCategory/IngredientCategory.js b/src/components/IngredientCategory/IngredientCategory.tsx similarity index 67% rename from src/components/IngredientCategory/IngredientCategory.js rename to src/components/IngredientCategory/IngredientCategory.tsx index 1cc1c08..66619c4 100644 --- a/src/components/IngredientCategory/IngredientCategory.js +++ b/src/components/IngredientCategory/IngredientCategory.tsx @@ -1,21 +1,28 @@ import IngredientCategoryStyles from './IngredientCategory.module.css'; -import { useMemo, useEffect } from 'react'; - -import PropTypes from 'prop-types'; -import IngredientsPropTypes from '../../utils/propTypes'; +import { useMemo, useEffect, FC, Ref, MouseEvent} from 'react'; import IngredientCard from '../IngredientCard/IngredientCard'; -import { useDispatch, useSelector } from 'react-redux'; +import { useDispatch, useSelector } from '../../services/hooks'; import { SET_ACTIVE_TAB } from '../../utils/constants'; +import { TIngredient } from '../../services/types/ingredients'; + +type TIngredientCategory = { + id: string; + title: string; + data: Array; + openIngredientModal: (e: MouseEvent) => void; + link: Ref; + inView: boolean; +} -function IngredientCategory({ id, title, data, openIngredientModal, link, inView }) { +const IngredientCategory: FC = ({ id, title, data, openIngredientModal, link, inView }) => { const dispatch = useDispatch(); const { addedIngredients } = useSelector(store => store.menu); const ingredientsCount = useMemo(() => { - return data.reduce((res, item) => { + return data?.reduce((res: {[name: string]: { count?: number}}, item) => { res[item._id] = {}; if (item.type === 'bun' && item._id === addedIngredients.bun?._id) { res[item._id].count = 1; @@ -39,7 +46,7 @@ function IngredientCategory({ id, title, data, openIngredientModal, link, inView {title}
    - {data.map((item) => { + {data?.map((item) => { return { const dispatch = useDispatch(); const { ingredientId } = useParams(); diff --git a/src/components/IngredientPreview/IngredientPreview.jsx b/src/components/IngredientPreview/IngredientPreview.tsx similarity index 82% rename from src/components/IngredientPreview/IngredientPreview.jsx rename to src/components/IngredientPreview/IngredientPreview.tsx index 85dd410..d53390c 100644 --- a/src/components/IngredientPreview/IngredientPreview.jsx +++ b/src/components/IngredientPreview/IngredientPreview.tsx @@ -1,8 +1,9 @@ import IngredientPreviewStyles from './IngredientPreview.module.css'; -import { useSelector } from 'react-redux'; +import { FC } from 'react'; +import { useSelector } from '../../services/hooks'; -function IngredientPreview({ id, hidedLength }) { +const IngredientPreview: FC<{id: string; hidedLength?: number}> = ({ id, hidedLength }) => { const { ingredientsById } = useSelector((state) => state.menu); return ( diff --git a/src/components/Main/Main.js b/src/components/Main/Main.tsx similarity index 89% rename from src/components/Main/Main.js rename to src/components/Main/Main.tsx index 324c5c0..da2120d 100644 --- a/src/components/Main/Main.js +++ b/src/components/Main/Main.tsx @@ -1,5 +1,5 @@ -import React, { useState } from 'react'; -import { useSelector, useDispatch } from 'react-redux'; +import { useState, FC, MouseEvent } from 'react'; +import { useSelector, useDispatch } from '../../services/hooks'; import { DndProvider } from "react-dnd"; import { HTML5Backend } from "react-dnd-html5-backend"; @@ -11,7 +11,7 @@ import OrderAccepted from '../OrderAccepted/OrderAccepted'; import {SHOW_INGREDIENT, HIDE_INGREDIENT} from '../../utils/constants'; -function Main() { +const Main: FC = () => { const dispatch = useDispatch(); const [isOrderDetailsOpened, setIsOrderDetailsOpened] = useState(false); @@ -27,7 +27,7 @@ function Main() { setIsOrderDetailsOpened(true); } - const openIngredientModal = (e) => { + const openIngredientModal = (e: MouseEvent) => { dispatch({type: SHOW_INGREDIENT, id: e.currentTarget.id}); } diff --git a/src/components/Modal/Modal.js b/src/components/Modal/Modal.tsx similarity index 70% rename from src/components/Modal/Modal.js rename to src/components/Modal/Modal.tsx index 2dc16b5..9316f21 100644 --- a/src/components/Modal/Modal.js +++ b/src/components/Modal/Modal.tsx @@ -1,16 +1,25 @@ -import React from 'react'; +import { useEffect, FC, ReactNode } from 'react'; import ReactDOM from 'react-dom'; -import PropTypes from 'prop-types'; import ModalOverlay from '../ModalOverlay/ModalOverlay'; import ModalStyles from './Modal.module.css'; import { CloseIcon } from '@ya.praktikum/react-developer-burger-ui-components'; -const modalsContainer = document.querySelector('#modals'); +const modalsContainer = document.querySelector('#modals') as HTMLElement; -function Modal({ title, onClose, children }) { +type TModalProps = { + title?: string; + onClose: () => void; + children: ReactNode; +} + +interface IKeyboardEvent { + key: string; +} - React.useEffect(() => { - const handleEscKeydown = (e) => { +const Modal: FC = ({ title, onClose, children }) => { + + useEffect(() => { + const handleEscKeydown = (e: IKeyboardEvent) => { e.key === 'Escape' && onClose(); }; @@ -36,10 +45,4 @@ function Modal({ title, onClose, children }) { ); }; -Modal.propTypes = { - title: PropTypes.string, - onClose: PropTypes.func.isRequired, - children: PropTypes.node -} - export default Modal; diff --git a/src/components/ModalOverlay/ModalOverlay.js b/src/components/ModalOverlay/ModalOverlay.tsx similarity index 52% rename from src/components/ModalOverlay/ModalOverlay.js rename to src/components/ModalOverlay/ModalOverlay.tsx index 756341d..2494911 100644 --- a/src/components/ModalOverlay/ModalOverlay.js +++ b/src/components/ModalOverlay/ModalOverlay.tsx @@ -1,16 +1,15 @@ -import React from "react"; -import PropTypes from 'prop-types'; +import { FC } from "react"; import ModalOverlayStyles from './ModalOverlay.module.css'; -function ModalOverlay({onClick}) { +type TModalProps = { + onClick: () => void; +} + +const ModalOverlay: FC = ({ onClick }) => { return (
    ) } -ModalOverlay.propTypes = { - onClick: PropTypes.func.isRequired -} - export default ModalOverlay; diff --git a/src/components/OrderAccepted/OrderAccepted.jsx b/src/components/OrderAccepted/OrderAccepted.tsx similarity index 88% rename from src/components/OrderAccepted/OrderAccepted.jsx rename to src/components/OrderAccepted/OrderAccepted.tsx index cb42103..c7182f9 100644 --- a/src/components/OrderAccepted/OrderAccepted.jsx +++ b/src/components/OrderAccepted/OrderAccepted.tsx @@ -1,10 +1,11 @@ +import { FC } from 'react'; import OrderAcceptedStyles from './OrderAccepted.module.css'; -import { useSelector } from 'react-redux'; +import { useSelector } from '../../services/hooks'; import done from '../../images/done.svg'; -function OrderAccepted() { +const OrderAccepted: FC = () => { const {orderId, orderRequest, orderFailed} = useSelector(state => state.menu); return ( @@ -24,4 +25,4 @@ function OrderAccepted() { ) }; -export default OrderAccepted; \ No newline at end of file +export default OrderAccepted; diff --git a/src/components/OrderDetails/OrderDetails.js b/src/components/OrderDetails/OrderDetails.tsx similarity index 81% rename from src/components/OrderDetails/OrderDetails.js rename to src/components/OrderDetails/OrderDetails.tsx index f7cd88f..36a1742 100644 --- a/src/components/OrderDetails/OrderDetails.js +++ b/src/components/OrderDetails/OrderDetails.tsx @@ -1,30 +1,39 @@ import OrderDetailsStyles from './OrderDetails.module.css'; -import { useEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import { useEffect, FC } from 'react'; +import { useDispatch, useSelector } from '../../services/hooks'; import { useLocation, useParams } from 'react-router-dom'; import IngredientPreview from '../IngredientPreview/IngredientPreview'; import { CurrencyIcon } from '@ya.praktikum/react-developer-burger-ui-components'; -import { ORDER_STATUSES, GET_ALL_ORDERS_URL, GET_USER_ORDERS_URL } from '../../utils/constants'; +import { ORDER_STATUSES, GET_ALL_ORDERS_URL, GET_USER_ORDERS_URL, WS_STATUS_ONLINE } from '../../utils/constants'; import { wsConnect, wsDisconnect } from '../../services/actions/ws'; import { getFormattedDate } from '../../utils/date'; import { getCookie } from '../../utils/cookie'; +import { TFeedData } from '../../services/types/order'; -function OrderDetails() { +type TIngredientCounts = { + total: number; + ingredients: { + [name: string]: number; + } +} + +const OrderDetails: FC = () => { const dispatch = useDispatch(); const location = useLocation(); - const { orders } = useSelector(state => state.ws.feed); + const { orders } = useSelector(({ ws }) => (ws.feed as TFeedData)); + const { ingredientsById } = useSelector(state => state.menu); const { orderId } = useParams(); const token = getCookie('accessToken'); let fullPageClass = location.state ? '' : 'fullPage'; - const currentOrder = orders?.find((el) => { + const currentOrder = orders.find((el) => { return el._id === orderId; }); - const ingredientCounts = currentOrder?.ingredients.reduce((res, item) => { + const ingredientCounts: TIngredientCounts | undefined = currentOrder ? currentOrder.ingredients.reduce((res: TIngredientCounts, item) => { if (item) { if (!res.ingredients[item]) { res.ingredients[item] = 0 @@ -35,7 +44,7 @@ function OrderDetails() { } return res; - }, {total: 0, ingredients: {}}); + }, {total: 0, ingredients: {}}) : undefined; useEffect(() => { if (!orders && location.pathname.includes('/feed')) { @@ -47,8 +56,8 @@ function OrderDetails() { } return () => { - if (!location.state) { - return dispatch(wsDisconnect()); + if (!location.state) { + dispatch(wsDisconnect()); } }; }, [dispatch]) @@ -56,7 +65,7 @@ function OrderDetails() { return ( <> { !currentOrder &&

    Обрабатываем запрос

    } - { currentOrder && + { currentOrder && ingredientCounts &&

    #{currentOrder.number}

    {currentOrder.name}

    diff --git a/src/components/OrderSnippet/OrderSnippet.jsx b/src/components/OrderSnippet/OrderSnippet.tsx similarity index 78% rename from src/components/OrderSnippet/OrderSnippet.jsx rename to src/components/OrderSnippet/OrderSnippet.tsx index bb4b40c..7e11c8a 100644 --- a/src/components/OrderSnippet/OrderSnippet.jsx +++ b/src/components/OrderSnippet/OrderSnippet.tsx @@ -1,32 +1,45 @@ +import { FC } from 'react'; import OrderSnippetStyles from './OrderSnippet.module.css'; import { Link, useLocation } from 'react-router-dom'; -import { useSelector } from 'react-redux'; +import { useSelector } from '../../services/hooks'; import { CurrencyIcon } from '@ya.praktikum/react-developer-burger-ui-components'; import IngredientPreview from '../IngredientPreview/IngredientPreview'; import { ORDER_STATUSES } from '../../utils/constants'; import { getFormattedDate } from '../../utils/date'; +import { TOrder } from '../../services/types/order'; -function OrderSnippet({ order, needDetails, link }) { +type TOrderSnippet = { + order: TOrder; + needDetails?: boolean; + link: string; +} + +type TIngredientsPreview = { + hidedLength: number; + maxLength: 6; + ingredients: Array; +} + +const OrderSnippet: FC = ({ order, needDetails, link }) => { const location = useLocation(); const { ingredientsById } = useSelector(state => state.menu); - const totalPrice = order.ingredients.reduce((res, item) => { - if (item) { - res += ingredientsById[item].price; + const totalPrice = order.ingredients.reduce((res, id) => { + if (id) { + res += ingredientsById[id].price; } return res; }, 0); - const ingredientsPreview = order.ingredients.reduceRight((res, item, index) => { + const ingredientsPreview: TIngredientsPreview = order.ingredients.reduceRight((res: TIngredientsPreview, item, index) => { if (item && index < res.maxLength) { res.ingredients.push(item); } return res; }, {hidedLength: order.ingredients.length - 6, maxLength: 6, ingredients: []}); - // console.log('OrderSnippet location', location); return (
    diff --git a/src/components/Orders/Orders.js b/src/components/Orders/Orders.tsx similarity index 62% rename from src/components/Orders/Orders.js rename to src/components/Orders/Orders.tsx index 263a709..6a4883f 100644 --- a/src/components/Orders/Orders.js +++ b/src/components/Orders/Orders.tsx @@ -1,29 +1,41 @@ import OrdersStyles from './Orders.module.css'; -import { GET_ALL_ORDERS_URL } from '../../utils/constants'; +import { GET_ALL_ORDERS_URL, WS_STATUS_ONLINE } from '../../utils/constants'; import { wsConnect, wsDisconnect } from '../../services/actions/ws'; -import { useEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import { useEffect, FC } from 'react'; +import { useDispatch, useSelector } from '../../services/hooks'; import OrderSnippet from '../OrderSnippet/OrderSnippet'; +import { TFeedData, TOrder } from '../../services/types/order'; +type TOrdersByStatus = { + done: Array; + progress: Array; + created: Array; + cancelled: Array; +}; -function Orders() { +const Orders: FC = () => { const dispatch = useDispatch(); - const { total, totalToday, orders } = useSelector( state => state.ws.feed); - const ordersByStatus = orders?.length && orders.reduce((res, item) => { + const feed = useSelector((state) => (state.ws.feed as TFeedData)); + + const total = feed ? feed.total : 0; + const totalToday = feed ? feed.totalToday : 0; + const orders = feed ? feed.orders : []; + + const ordersByStatus: TOrdersByStatus | undefined = orders?.length ? (orders as Array).reduce((res: TOrdersByStatus, item) => { if (res[item.status].length < 20) { res[item.status].push(item); } return res; - }, {done: [], progress: [], created: []}); + }, {done: [], progress: [], created: [], cancelled: []}) : undefined; - const columnCountDone = Math.floor(ordersByStatus?.done.length / 10); - const columnCountProgress = Math.floor(ordersByStatus?.progress.length / 10); + const columnCountDone = (ordersByStatus && ordersByStatus.done.length > 10 )? Math.floor(ordersByStatus.done.length / 10) : 1; + const columnCountProgress = (ordersByStatus && ordersByStatus.progress.length > 10 )? Math.floor(ordersByStatus.progress.length / 10) : 1; useEffect(() => { dispatch(wsConnect(GET_ALL_ORDERS_URL)); - return () => dispatch(wsDisconnect()); + return () => { dispatch(wsDisconnect()); } }, [dispatch]) return ( @@ -31,8 +43,8 @@ function Orders() {

    Лента заказов

    {orders && - orders.map((item, index) => { - return + orders.map((order: TOrder) => { + return }) }
    @@ -40,7 +52,7 @@ function Orders() {

    Готовы:

    -
      +
        {ordersByStatus && ordersByStatus.done.map((item, index) => { return (
      • {item.number}
      • @@ -50,7 +62,7 @@ function Orders() {

    В работе:

    -
      +
        {ordersByStatus && ordersByStatus.progress.map((item, index) => { return (
      • {item.number}
      • diff --git a/src/components/ProtectedRoute/ProtectedRoute.js b/src/components/ProtectedRoute/ProtectedRoute.tsx similarity index 75% rename from src/components/ProtectedRoute/ProtectedRoute.js rename to src/components/ProtectedRoute/ProtectedRoute.tsx index 34f6518..d375fdd 100644 --- a/src/components/ProtectedRoute/ProtectedRoute.js +++ b/src/components/ProtectedRoute/ProtectedRoute.tsx @@ -1,10 +1,16 @@ +import { FC, ReactElement } from 'react'; import { Navigate, useLocation } from 'react-router-dom'; -import { useSelector } from 'react-redux'; +import { useSelector } from '../../services/hooks'; -import PropTypes from 'prop-types'; import { getCookie } from '../../utils/cookie'; -function ProtectedRoute({element, isAuthPage, accessFrom}) { +type TProtectedRoute = { + element: ReactElement; + isAuthPage?: boolean; + accessFrom?: string; +} + +const ProtectedRoute: FC = ({element, isAuthPage, accessFrom}) => { const accessToken = getCookie('accessToken'); const refreshToken = getCookie('token'); const { isAuthSuccess, isUserLoaded } = useSelector(state => state.user); @@ -14,7 +20,8 @@ function ProtectedRoute({element, isAuthPage, accessFrom}) { if (!isUserLoaded && (accessToken || refreshToken)) { return

        Загрузка

        -} + } + if (isAuthPage && isAuthSuccess) { return ; } @@ -30,10 +37,4 @@ function ProtectedRoute({element, isAuthPage, accessFrom}) { return element; } -ProtectedRoute.propTypes = { - element: PropTypes.node.isRequired, - isAuthPage: PropTypes.bool, - accessFrom: PropTypes.string -} - export default ProtectedRoute; \ No newline at end of file diff --git a/src/components/TotalPrice/TotalPrice.js b/src/components/TotalPrice/TotalPrice.js deleted file mode 100644 index 43db974..0000000 --- a/src/components/TotalPrice/TotalPrice.js +++ /dev/null @@ -1,34 +0,0 @@ -import TotalPriceStyles from './TotalPrice.module.css'; - -import { useMemo } from 'react'; -import { useSelector } from 'react-redux'; - -import { CurrencyIcon } from '@ya.praktikum/react-developer-burger-ui-components'; - - -function TotalPrice() { - - const {addedIngredients} = useSelector(state => state.menu); - - const total = useMemo(() => { - let price = addedIngredients.bun ? addedIngredients.bun.price * 2 : 0; - - if (!addedIngredients.others.length) { - return price; - } - - return addedIngredients.others.reduce((res, item) => { - res += item.price; - - return res; - }, price); - }, [addedIngredients.bun, addedIngredients.others]); - - return ( -
        - {total} - -
        - ) -} -export default TotalPrice; \ No newline at end of file diff --git a/src/components/TotalPrice/TotalPrice.tsx b/src/components/TotalPrice/TotalPrice.tsx new file mode 100644 index 0000000..965f702 --- /dev/null +++ b/src/components/TotalPrice/TotalPrice.tsx @@ -0,0 +1,28 @@ +import TotalPriceStyles from './TotalPrice.module.css'; + +import { useMemo, FC } from 'react'; +import { useSelector } from '../../services/hooks'; +import { TRootState } from '../../services/types/index'; + +import { CurrencyIcon } from '@ya.praktikum/react-developer-burger-ui-components'; +import { TIngredient } from '../../services/types/ingredients'; + +const TotalPrice: FC = () => { + + const { addedIngredients: { bun, others } } = useSelector((state: TRootState) => state.menu); + + const total = useMemo(() => ( + (others as TIngredient[]).reduce( + (res: number, {price}: TIngredient) => res += price, + bun ? bun.price * 2 : 0 + )), [bun, others]); + + return ( +
        + {total} + +
        + ) +} + +export default TotalPrice; diff --git a/src/index.js b/src/index.js deleted file mode 100644 index a4b9364..0000000 --- a/src/index.js +++ /dev/null @@ -1,49 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import './index.css'; -import App from './components/App/App'; -import reportWebVitals from './reportWebVitals'; -import { rootReducer } from './services/reducers'; -import { compose, createStore, applyMiddleware } from 'redux'; -import thunk from 'redux-thunk'; -import { Provider } from 'react-redux'; -import { BrowserRouter } from 'react-router-dom'; -import { socketMiddleware } from './services/middlewares/ws'; -import { wsConnect, wsDisconnect, wsConnecting, wsOpen, wsClose, wsError, wsMessage } from './services/actions/ws'; - -const root = ReactDOM.createRoot( - document.getElementById('root') -); - -const composeEnhancers = - typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ - ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) - : compose; - -const wsActions = { - wsConnect, - wsDisconnect, - wsConnecting, - onOpen: wsOpen, - onClose: wsClose, - onError: wsError, - onMessage: wsMessage -} -const enhancer = composeEnhancers(applyMiddleware(thunk, socketMiddleware(wsActions))); - -const store = createStore(rootReducer, enhancer); - -root.render( - // - - - - - - // -); - -// If you want to start measuring performance in your app, pass a function -// to log results (for example: reportWebVitals(console.log)) -// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -reportWebVitals(); diff --git a/src/index.tsx b/src/index.tsx new file mode 100644 index 0000000..4964e82 --- /dev/null +++ b/src/index.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import './index.css'; +import App from './components/App/App'; +import reportWebVitals from './reportWebVitals'; +import { Provider } from 'react-redux'; +import { BrowserRouter } from 'react-router-dom'; +import { store } from './services/store'; + +const root = ReactDOM.createRoot( + document.getElementById('root') as HTMLElement +); + +root.render( + + + + + +); + +// If you want to start measuring performance in your app, pass a function +// to log results (for example: reportWebVitals(console.log)) +// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals +reportWebVitals(); diff --git a/src/pages/forgotPassword.jsx b/src/pages/forgotPassword.tsx similarity index 87% rename from src/pages/forgotPassword.jsx rename to src/pages/forgotPassword.tsx index 7e8a6c6..fc4dd0e 100644 --- a/src/pages/forgotPassword.jsx +++ b/src/pages/forgotPassword.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import { ChangeEvent, FormEvent } from 'react'; +import { useDispatch, useSelector } from '../services/hooks'; import ForgotPasswordPageStyles from './login.module.css'; import { Link, Navigate, useLocation } from 'react-router-dom'; @@ -12,13 +12,13 @@ export function ForgotPasswordPage() { const {email, canResetPassword} = useSelector(state => state.user); - const onFormSubmit = (e) => { + const onFormSubmit = (e: FormEvent) => { e.preventDefault(); dispatch(forgotPassword(email)); } - const onFormChange = (e) => { + const onFormChange = (e: ChangeEvent) => { dispatch(setRegisterFormValue(e.target.name, e.target.value)); } diff --git a/src/pages/login.jsx b/src/pages/login.tsx similarity index 89% rename from src/pages/login.jsx rename to src/pages/login.tsx index f888590..8fffe2e 100644 --- a/src/pages/login.jsx +++ b/src/pages/login.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import { ChangeEvent, FormEvent } from 'react'; +import { useDispatch, useSelector } from '../services/hooks'; import LoginPageStyles from './login.module.css'; import { Link, Navigate } from 'react-router-dom'; @@ -11,13 +11,13 @@ export function LoginPage() { const {email, password, isAuthSuccess} = useSelector(state => state.user); - const onFormSubmit = (e) => { + const onFormSubmit = (e: FormEvent) => { e.preventDefault(); dispatch(loginUser({email, password})); } - const onFormChange = (e) => { + const onFormChange = (e: ChangeEvent) => { dispatch(setRegisterFormValue(e.target.name, e.target.value)); } diff --git a/src/pages/logoutPage.jsx b/src/pages/logoutPage.tsx similarity index 86% rename from src/pages/logoutPage.jsx rename to src/pages/logoutPage.tsx index c57c6b7..0b66a8d 100644 --- a/src/pages/logoutPage.jsx +++ b/src/pages/logoutPage.tsx @@ -1,4 +1,5 @@ -import { useDispatch, useSelector } from 'react-redux'; +import { FormEvent } from 'react'; +import { useDispatch, useSelector } from '../services/hooks'; import LogoutPageStyles from './login.module.css'; import { Navigate } from 'react-router-dom'; @@ -10,7 +11,7 @@ export function LogoutPage() { const {isAuthSuccess} = useSelector(state => state.user); - const onFormSubmit = (e) => { + const onFormSubmit = (e: FormEvent) => { e.preventDefault(); dispatch(logoutUser({})); diff --git a/src/pages/notFound.jsx b/src/pages/notFound.tsx similarity index 93% rename from src/pages/notFound.jsx rename to src/pages/notFound.tsx index 8516a85..171d9ff 100644 --- a/src/pages/notFound.jsx +++ b/src/pages/notFound.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import NotFoundPageStyles from './notFound.module.css'; import { Link } from 'react-router-dom'; @@ -12,4 +11,4 @@ export function NotFoundPage() {

        Зато у нас можно подкрепиться вкусными бургерами

    ); -} \ No newline at end of file +} diff --git a/src/pages/profile.jsx b/src/pages/profile.tsx similarity index 81% rename from src/pages/profile.jsx rename to src/pages/profile.tsx index c18c08f..442cd0f 100644 --- a/src/pages/profile.jsx +++ b/src/pages/profile.tsx @@ -1,4 +1,5 @@ -import { useDispatch, useSelector } from 'react-redux'; +import { FormEvent, ChangeEvent } from 'react'; +import { useDispatch, useSelector } from '../services/hooks'; import ProfilePageStyles from './profile.module.css'; import { NavLink } from 'react-router-dom'; @@ -10,21 +11,21 @@ export function ProfilePage() { const dispatch = useDispatch(); const {name, email, password, isUserEdited, editUserFailed} = useSelector(state => state.user); - const onFormChange = (e) => { + const onFormChange = (e: ChangeEvent) => { dispatch(setEditUserForm(e.target.name, e.target.value)); } - const onFormSubmit = (e) => { + const onFormSubmit = (e: FormEvent) => { e.preventDefault(); dispatch(editUser({name, email, password})); } - const onReset = (e) => { + const onReset = () => { dispatch({type: RESET_EDIT_USER_FORM}); } - const active = ProfilePageStyles.active + ' ' + ProfilePageStyles.link + ' pt-4 pb-4 text text_type_main-medium'; - const link = ProfilePageStyles.link + ' pt-4 pb-4 text text_type_main-medium text_color_inactive'; + const active: string = ProfilePageStyles.active + ' ' + ProfilePageStyles.link + ' pt-4 pb-4 text text_type_main-medium'; + const link: string = ProfilePageStyles.link + ' pt-4 pb-4 text text_type_main-medium text_color_inactive'; return (
    @@ -44,7 +45,7 @@ export function ProfilePage() {
    - + { isUserEdited &&
    diff --git a/src/pages/register.jsx b/src/pages/register.tsx similarity index 88% rename from src/pages/register.jsx rename to src/pages/register.tsx index 00c0633..3d89158 100644 --- a/src/pages/register.jsx +++ b/src/pages/register.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import { FormEvent, ChangeEvent } from 'react'; +import { useDispatch, useSelector } from '../services/hooks'; import RegisterPageStyles from './login.module.css'; import { Link, Navigate } from 'react-router-dom'; @@ -11,13 +11,13 @@ export function RegisterPage() { const {name, email, password, isAuthSuccess} = useSelector(state => state.user); - const addUserHandler = (e) => { + const addUserHandler = (e: FormEvent) => { e.preventDefault(); dispatch(addUser({name, email, password})); } - const onFormChange = (e) => { + const onFormChange = (e: ChangeEvent) => { dispatch(setRegisterFormValue(e.target.name, e.target.value)); } @@ -44,4 +44,4 @@ export function RegisterPage() {
    ); -} \ No newline at end of file +} diff --git a/src/pages/resetPassword.jsx b/src/pages/resetPassword.tsx similarity index 88% rename from src/pages/resetPassword.jsx rename to src/pages/resetPassword.tsx index 11265df..352eb42 100644 --- a/src/pages/resetPassword.jsx +++ b/src/pages/resetPassword.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import { FormEvent, ChangeEvent } from 'react'; +import { useDispatch, useSelector } from '../services/hooks'; import ResetPasswordPageStyles from './login.module.css'; import { Link, Navigate } from 'react-router-dom'; @@ -11,13 +11,13 @@ export function ResetPasswordPage() { const {password, code, resetPasswordSuccess} = useSelector(state => state.user); - const onFormSubmit = (e) => { + const onFormSubmit = (e: FormEvent) => { e.preventDefault(); dispatch(resetPassword(password, code)); } - const onFormChange = (e) => { + const onFormChange = (e: ChangeEvent) => { dispatch(setRegisterFormValue(e.target.name, e.target.value)); } @@ -43,4 +43,4 @@ export function ResetPasswordPage() {
    ); -} \ No newline at end of file +} diff --git a/src/pages/userOrdersPage.jsx b/src/pages/userOrdersPage.tsx similarity index 78% rename from src/pages/userOrdersPage.jsx rename to src/pages/userOrdersPage.tsx index b7505a0..00696d7 100644 --- a/src/pages/userOrdersPage.jsx +++ b/src/pages/userOrdersPage.tsx @@ -1,10 +1,10 @@ -import React, { useEffect } from 'react'; +import { useEffect } from 'react'; +import { useDispatch, useSelector } from '../services/hooks'; import UserOrdersPageStyles from './userOrdersPage.module.css'; import { NavLink } from 'react-router-dom'; -import { useDispatch, useSelector } from 'react-redux'; import { wsConnect, wsDisconnect } from '../services/actions/ws'; -import { GET_USER_ORDERS_URL } from '../utils/constants'; +import { GET_USER_ORDERS_URL, WS_STATUS_ONLINE } from '../utils/constants'; import OrderSnippet from '../components/OrderSnippet/OrderSnippet'; import { getCookie } from '../utils/cookie'; @@ -19,10 +19,12 @@ export function UserOrdersPage() { useEffect(() => { dispatch(wsConnect(`${GET_USER_ORDERS_URL}?token=${token}`)); - return () => dispatch(wsDisconnect()); + return () => { dispatch(wsDisconnect()); } }, [dispatch]); - const { orders } = useSelector(state => state.ws.feed); + const feed = useSelector(({ ws }) => ( + ws.status === WS_STATUS_ONLINE && ws.feed?.success ? ws.feed : undefined + )); return (
    @@ -41,8 +43,8 @@ export function UserOrdersPage() {

    - {!orders &&
    Заказов пока нет
    } - {orders && orders.map((item, index) => { + {!feed &&
    Заказов пока нет
    } + {feed?.orders && feed.orders.map((item, index) => { return () })}
    diff --git a/src/services/actions/ingredients.js b/src/services/actions/ingredients.js deleted file mode 100644 index 2dcbc57..0000000 --- a/src/services/actions/ingredients.js +++ /dev/null @@ -1,86 +0,0 @@ -import { getIngredientsRequest, sendOrderRequest } from "../../utils/api"; -import { - GET_INGREDIENTS_REQUEST, - GET_INGREDIENTS_SUCCESS, - GET_INGREDIENTS_FAILED, - GET_ORDER_REQUEST, - GET_ORDER_SUCCESS, - GET_ORDER_FAILED - } from '../../utils/constants'; - import { getAccessToken, getCookie, setCookie } from "../../utils/cookie"; - import { getUserRequest, refreshTokenRequest } from "../../utils/api"; - -export function getIngredients() { - return function(dispatch) { - dispatch({type: GET_INGREDIENTS_REQUEST}); - - getIngredientsRequest() - .then((res) => { - if (res && res.success) { - const ingredientsById = {}; - const ingredientsByType = {}; - - - res.data.forEach((item) => { - ingredientsById[item._id] = item; - (ingredientsByType[item.type] || (ingredientsByType[item.type] = [])).push(item); - }); - - dispatch({ - type: GET_INGREDIENTS_SUCCESS, - ingredientsByType, - ingredientsById - }) - } else { - dispatch({type: GET_INGREDIENTS_FAILED}); - } - }) - .catch((err) => { - dispatch({type: GET_INGREDIENTS_FAILED}); - }) - } -} - -export function sendOrder(ids) { - return function(dispatch) { - dispatch({type: GET_ORDER_REQUEST}); - const accessToken = getCookie('accessToken'); - - sendOrderRequest(ids, accessToken) - .then((res) => { - if (res && res.success) { - dispatch({ - type: GET_ORDER_SUCCESS, - orderId: res.order.number - }) - } else if (res.message === "jwt expired") { - - refreshTokenRequest(getCookie('token')) - .then((res) => { - if (res && res.success) { - setCookie('accessToken', getAccessToken(res.accessToken)); - setCookie('token', res.refreshToken); - - sendOrderRequest(ids, getAccessToken(res.accessToken)) - .then((res) => { - if (res && res.success) { - dispatch({ - type: GET_ORDER_SUCCESS, - orderId: res.order.number - }) - } - }) - .catch((err) => { - dispatch({type: GET_ORDER_FAILED}); - }) - } - }) - } else { - dispatch({type: GET_ORDER_FAILED}); - } - }) - .catch((err) => { - dispatch({type: GET_ORDER_FAILED}); - }) - } -} diff --git a/src/services/actions/ingredients.tsx b/src/services/actions/ingredients.tsx new file mode 100644 index 0000000..5f97169 --- /dev/null +++ b/src/services/actions/ingredients.tsx @@ -0,0 +1,74 @@ +import { AppDispatch, AppThunk } from '../types/index'; +import { getIngredientsRequest, sendOrderRequest } from "../../utils/api"; +import { TIngredient, TIngredientsByType, TIngredientsById } from "../types/ingredients"; +import { getAccessToken, getCookie, setCookie } from "../../utils/cookie"; +import { refreshTokenRequest } from "../../utils/api"; +import { + getIngredientsSuccessAction, + getIngredientsFailedAction, + getIngredientsAction, + sendOrderAction, + sendOrderSuccessAction, + sendOrderFailedAction +} from '../types/ingredients'; + +export const getIngredients: AppThunk = () => (dispatch: AppDispatch) => { + dispatch(getIngredientsAction()); + + getIngredientsRequest() + .then((res) => { + if (res && res.success) { + const ingredientsById: TIngredientsById = {}; + const ingredientsByType: TIngredientsByType = {}; + + res.data.forEach((item: TIngredient) => { + ingredientsById[item._id] = item; + (ingredientsByType[item.type] || (ingredientsByType[item.type] = [])).push(item); + }); + + dispatch(getIngredientsSuccessAction(ingredientsByType, ingredientsById)) + } else { + throw new Error('Ошибка: ' + res.message); + } + }) + .catch(() => { + dispatch(getIngredientsFailedAction()); + }) +} + +export const sendOrder: AppThunk = (ids: Array) => (dispatch: AppDispatch) => { + dispatch(sendOrderAction()); + + const accessToken: string = getCookie('accessToken'); + + sendOrderRequest(ids, accessToken) + .then((res) => { + if (res && res.success) { + dispatch(sendOrderSuccessAction(res.order.number)); + } else if (res.message === "jwt expired") { + + refreshTokenRequest(getCookie('token')) + .then((res) => { + if (res && res.success) { + setCookie('accessToken', getAccessToken(res.accessToken)); + setCookie('token', res.refreshToken); + + sendOrderRequest(ids, getAccessToken(res.accessToken)) + .then((res) => { + if (res && res.success) { + dispatch(sendOrderSuccessAction(res.order.number)); + } + }) + .catch(() => { + dispatch(sendOrderFailedAction()); + }) + } + }) + } else { + dispatch(sendOrderFailedAction()); + } + }) + .catch(() => { + dispatch(sendOrderFailedAction()); + }) +} diff --git a/src/services/actions/user.js b/src/services/actions/user.js deleted file mode 100644 index 520a54a..0000000 --- a/src/services/actions/user.js +++ /dev/null @@ -1,217 +0,0 @@ -import { - addUserRequest, - refreshTokenRequest, - loginRequest, - logoutRequest, - getUserRequest, - editUserRequest, - passwordForgotRequest, - passwordResetRequest -} from "../../utils/api"; -import { setCookie, getCookie, getAccessToken } from "../../utils/cookie"; -import { - SET_REGISTER_FORM_VALUE, - SET_EDIT_USER_FORM, - - ADD_USER_REQUEST, - ADD_USER_SUCCESS, - ADD_USER_FAILED, - - LOGIN_USER_REQUEST, - LOGIN_USER_SUCCESS, - LOGIN_USER_FAILED, - - GET_USER_SUCCESS, - GET_USER_FAILED, - - EDIT_USER_SUCCESS, - EDIT_USER_FAILED, - - FORGOT_PASSWORD_SUCCESS, - FORGOT_PASSWORD_FAILED, - - RESET_PASSWORD_SUCCESS, - RESET_PASSWORD_FAILED, - - LOGOUT_USER_SUCCESS, - LOGOUT_USER_FAILED, - GET_USER_ORDERS - } from '../../utils/constants'; - - -export const setRegisterFormValue = (field, value) => ({ - type: SET_REGISTER_FORM_VALUE, - field, - value -}); - -export const setEditUserForm = (field, value) => ({ - type: SET_EDIT_USER_FORM, - field, - value -}); - -// Добавление нового пользователя -export function addUser(user) { - return function(dispatch) { - dispatch({type: ADD_USER_REQUEST}); - - addUserRequest(user) - .then((res) => { - if (res && res.success) { - const accessToken = getAccessToken(res.accessToken); - - setCookie('token', res.refreshToken); - setCookie('accessToken', accessToken); - dispatch({ - type: ADD_USER_SUCCESS, - user: res.user - }) - }}) - .catch((err) => { - dispatch({type: ADD_USER_FAILED}); - }) - } -} - -// Вход уже существующего пользователя -export function loginUser(user) { - return function(dispatch) { - dispatch({type: LOGIN_USER_REQUEST}); - - loginRequest(user) - .then((res) => { - if (res && res.success) { - const accessToken = getAccessToken(res.accessToken); - - setCookie('token', res.refreshToken); - setCookie('accessToken', accessToken); - - dispatch({ - type: LOGIN_USER_SUCCESS, - user: res.user - }) - }}) - .catch((err) => { - dispatch({type: LOGIN_USER_FAILED, errorMessage: err.message}); - }) - } -} - -// Получение информации о пользователе -export function getUser() { - return function(dispatch) { - getRequestWithRetry( - getUserRequest, - ({user}) => dispatch({type: GET_USER_SUCCESS, user}), - () => dispatch({type: GET_USER_FAILED}) - ); - } -} - -export function editUser(user) { - return function(dispatch) { - getRequestWithRetry( - editUserRequest, - ({user}) => dispatch({type: EDIT_USER_SUCCESS, user}), - () => dispatch({type: EDIT_USER_FAILED}), - {user} - ); - } -} - -export function forgotPassword(email) { - return function(dispatch) { - - passwordForgotRequest(email) - .then((res) => { - if (res && res.success) { - dispatch({ - type: FORGOT_PASSWORD_SUCCESS, - user: res.user - }); - } - }) - .catch((err) => { - dispatch({type: FORGOT_PASSWORD_FAILED}); - }) - } - -} - -export function resetPassword(password, code) { - return function(dispatch) { - - passwordResetRequest(password, code) - .then((res) => { - if (res && res.success) { - dispatch({type: RESET_PASSWORD_SUCCESS}) - } - }) - .catch((err) => { - dispatch({type: RESET_PASSWORD_FAILED}); - }) - } - -} - -export function logoutUser() { - return function(dispatch) { - getRequestWithRetry( - logoutRequest, - (res) => { - setCookie('accessToken', '', {Expires: 0}); - setCookie('token', '', {Expires: 0}); - dispatch({ - type: LOGOUT_USER_SUCCESS - }) - }, - ({message}) => { - dispatch({type: LOGOUT_USER_FAILED, errorMessage: message}); - }, - {token: getCookie('token')} - ) - } -} - -/** - * @param {function} getRequest - запрос для ретрая - * @param {object} options - параметры запроса - * @param {string} options.accessToken - токен авторизации - * @param {string} onSuccess - функция для обработки успешного результата - * @param {string} onFail - функция для обработки ошибок - */ -function getRequestWithRetry(getRequest, onSuccess, onFail, options = {}) { - options.accessToken = getCookie('accessToken'); - - getRequest(options) - .then((res) => { - if (res && res.success) { - onSuccess(res); - }}) - .catch((err) => { - if (err.cause.message === "jwt expired") { - return refreshTokenRequest(getCookie('token')) - .then((res) => { - if (res && res.success) { - setCookie('accessToken', getAccessToken(res.accessToken)); - setCookie('token', res.refreshToken); - - options.accessToken = getAccessToken(res.accessToken); - - return getRequest(options) - .then((res) => { - if (res && res.success) { - onSuccess(res) - } - }) - } - }) - } - - return err; - }) - .catch((err) => { - onFail(err); - }) -} diff --git a/src/services/actions/user.tsx b/src/services/actions/user.tsx new file mode 100644 index 0000000..37c0431 --- /dev/null +++ b/src/services/actions/user.tsx @@ -0,0 +1,196 @@ +import { AppDispatch, AppThunk } from '../types/index'; +import { + addUserRequest, + refreshTokenRequest, + loginRequest, + logoutRequest, + getUserRequest, + editUserRequest, + passwordForgotRequest, + passwordResetRequest +} from "../../utils/api"; +import { setCookie, getCookie, getAccessToken } from "../../utils/cookie"; +import { + TUser, + addUserAction, + addUserSuccessAction, + addUserFailedAction, + loginUserAction, + loginUserSuccessAction, + loginUserFailedAction, + getUserSuccessAction, + getUserFailedAction, + editUserSuccessAction, + editUserFailedAction, + forgotPasswordSuccessAction, + forgotPasswordFailedAction, + resetPasswordSuccessAction, + resetPasswordFailedAction, + logoutUserSuccessAction, + logoutUserFailedAction + } from "../types/user"; + import { + requestRetryOnFail, + requestRetryOnSuccess , +} from "../types/request"; +import { + SET_REGISTER_FORM_VALUE, + SET_EDIT_USER_FORM, +} from '../../utils/constants'; + + + +export const setRegisterFormValue = (field: string, value: string) => ({ + type: SET_REGISTER_FORM_VALUE, + field, + value +}); + +export const setEditUserForm = (field: string, value: string) => ({ + type: SET_EDIT_USER_FORM, + field, + value +}); + +// Добавление нового пользователя +export function addUser(user: TUser) { + return function(dispatch: AppDispatch) { + dispatch(addUserAction()); + + addUserRequest(user) + .then((res) => { + if (res && res.success) { + const accessToken = getAccessToken(res.accessToken); + + setCookie('token', res.refreshToken); + setCookie('accessToken', accessToken); + + dispatch(addUserSuccessAction(res.user)) + }}) + .catch(() => { + dispatch(addUserFailedAction()); + }) + } +} + +// Вход уже существующего пользователя +export const loginUser: AppThunk = (user: TUser) => (dispatch: AppDispatch) => { + dispatch(loginUserAction()); + + loginRequest(user) + .then((res) => { + if (res && res.success) { + const accessToken: string = getAccessToken(res.accessToken); + + setCookie('token', res.refreshToken); + setCookie('accessToken', accessToken); + + dispatch(loginUserSuccessAction(res.user)); + }}) + .catch((err) => { + dispatch(loginUserFailedAction(err.message)); + }) +} + +// Получение информации о пользователе +export function getUser() { + return function(dispatch: AppDispatch) { + getRequestWithRetry( + (accessToken) => getUserRequest(accessToken), + ({user}) => dispatch(getUserSuccessAction(user)), + () => dispatch(getUserFailedAction()) + ); + } +} + +// Редактирование информации о пользователе +export function editUser(user: TUser) { + return function(dispatch: AppDispatch) { + getRequestWithRetry( + (accessToken) => editUserRequest(accessToken, user), + ({user}) => dispatch(editUserSuccessAction(user)), + () => dispatch(editUserFailedAction()) + ); + } +} + +export const forgotPassword: AppThunk = (email: string) => (dispatch: AppDispatch) => { + passwordForgotRequest(email) + .then((res) => { + if (res && res.success) { + dispatch(forgotPasswordSuccessAction()); + } + }) + .catch(() => { + dispatch(forgotPasswordFailedAction()); + }) +} + +export function resetPassword(password: string, code: string) { + return function(dispatch: AppDispatch) { + + passwordResetRequest(password, code) + .then((res) => { + if (res && res.success) { + dispatch(resetPasswordSuccessAction()) + } + }) + .catch(() => { + dispatch(resetPasswordFailedAction()); + }) + } + +} + +export const logoutUser: AppThunk = () => (dispatch: AppDispatch) => { + getRequestWithRetry( + (accessToken) => logoutRequest(accessToken, getCookie('token')), + () => { + setCookie('accessToken', '', {expires: 0}); + setCookie('token', '', {expires: 0}); + dispatch(logoutUserSuccessAction()) + }, + ({message}) => { + dispatch(logoutUserFailedAction(message)); + } + ) +} + +/** + * @param {function} getRequest - запрос для ретрая + * @param {string} options.accessToken - токен авторизации + * @param {string} onSuccess - функция для обработки успешного результата + * @param {string} onFail - функция для обработки ошибок + */ + +function getRequestWithRetry(getRequest: ((accessToken: string) => Promise), onSuccess: requestRetryOnSuccess, onFail: requestRetryOnFail) { + + getRequest(getCookie('accessToken')) + .then((res: any) => { + if (res && res.success) { + onSuccess(res); + }}) + .catch((err: any) => { + if (err.cause.message === "jwt expired") { + return refreshTokenRequest(getCookie('token')) + .then((res) => { + if (res && res.success) { + setCookie('accessToken', getAccessToken(res.accessToken)); + setCookie('token', res.refreshToken); + + return getRequest(getAccessToken(res.accessToken)) + .then((res: any) => { + if (res && res.success) { + onSuccess(res) + } + }) + } + }) + } + + return err; + }) + .catch((err: any) => { + onFail(err); + }) +} diff --git a/src/services/actions/ws.js b/src/services/actions/ws.js deleted file mode 100644 index 3a2be81..0000000 --- a/src/services/actions/ws.js +++ /dev/null @@ -1,36 +0,0 @@ -import { createAction } from '@reduxjs/toolkit'; -import { refreshTokenRequest } from '../../utils/api'; -import { setCookie, getCookie, getAccessToken } from '../../utils/cookie'; -import { GET_USER_ORDERS_URL, WS_MESSAGE } from '../../utils/constants'; - - -export const wsConnect = createAction('WS_CONNECT'); -export const wsDisconnect = createAction('WS_DISCONNECT'); -export const wsConnecting = createAction('WS_CONNECTING'); -export const wsOpen = createAction('WS_OPEN'); -export const wsClose = createAction('WS_CLOSE'); -export const wsError = createAction('WS_ERROR'); - -export const wsMessage = (action) => (dispatch) => { - - dispatch({type: WS_MESSAGE, payload: action}); - - if (action.message === 'Invalid or missing token') { - dispatch(wsDisconnect()); - refreshTokenRequest(getCookie('token')) - .then((res) => { - if (res && res.success) { - setCookie('accessToken', getAccessToken(res.accessToken)); - setCookie('token', res.refreshToken); - - dispatch(wsConnect(`${GET_USER_ORDERS_URL}?token=${getCookie('accessToken')}`)); - } else { - throw new Error('Произошла ошибка ', res.message); - } - - }) - .catch((err) => { - console.log('error!', err); - }); - } -} diff --git a/src/services/actions/ws.tsx b/src/services/actions/ws.tsx new file mode 100644 index 0000000..710b31b --- /dev/null +++ b/src/services/actions/ws.tsx @@ -0,0 +1,53 @@ +import { AppDispatch, AppThunk } from '../types/index'; +import { createAction } from '@reduxjs/toolkit'; +import { refreshTokenRequest } from '../../utils/api'; +import { setCookie, getCookie, getAccessToken } from '../../utils/cookie'; +import { + GET_USER_ORDERS_URL, + WS_CONNECT, + WS_DISCONNECT, + WS_CONNECTING, + WS_OPEN, + WS_CLOSE, + WS_ERROR +} from '../../utils/constants'; +import { + TWSMessage, + wsMessageAction +} from '../types/ws'; + +function withPayloadType() { + return (t: T) => ({ payload: t }) +} +export const wsConnect = createAction(WS_CONNECT, withPayloadType()); +export const wsDisconnect = createAction(WS_DISCONNECT); +export const wsConnecting = createAction(WS_CONNECTING); +export const wsOpen = createAction(WS_OPEN); +export const wsClose = createAction(WS_CLOSE); +export const wsError = createAction(WS_ERROR); + +export const wsMessage: AppThunk = (action: TWSMessage) => (dispatch: AppDispatch) => { + dispatch(wsMessageAction(action)); + + if (!action.success) { + if (action.message === 'Invalid or missing token') { + dispatch(wsDisconnect()); + refreshTokenRequest(getCookie('token')) + .then((res) => { + if (res && res.success) { + setCookie('accessToken', getAccessToken(res.accessToken)); + setCookie('token', res.refreshToken); + + dispatch(wsConnect(`${GET_USER_ORDERS_URL}?token=${getCookie('accessToken')}`)); + } else { + throw new Error('Произошла ошибка '); + } + + }) + .catch((err) => { + console.log('error!', err); + }); + } + } + +} diff --git a/src/services/hooks.tsx b/src/services/hooks.tsx new file mode 100644 index 0000000..ba9691a --- /dev/null +++ b/src/services/hooks.tsx @@ -0,0 +1,10 @@ +import { + TypedUseSelectorHook, + useSelector as selectorHook, + useDispatch as dispatchHook +} from 'react-redux'; +import { AppDispatch, AppThunk } from './types/index'; +import { TRootState } from './types'; + +export const useSelector: TypedUseSelectorHook = selectorHook; +export const useDispatch = () => dispatchHook(); diff --git a/src/services/reducers/index.js b/src/services/reducers/index.tsx similarity index 74% rename from src/services/reducers/index.js rename to src/services/reducers/index.tsx index 38b4237..d297548 100644 --- a/src/services/reducers/index.js +++ b/src/services/reducers/index.tsx @@ -5,9 +5,9 @@ import { GET_INGREDIENTS_REQUEST, GET_INGREDIENTS_SUCCESS, GET_INGREDIENTS_FAILED, - GET_ORDER_REQUEST, - GET_ORDER_SUCCESS, - GET_ORDER_FAILED, + SEND_ORDER_REQUEST, + SEND_ORDER_SUCCESS, + SEND_ORDER_FAILED, SHOW_INGREDIENT, HIDE_INGREDIENT, ADD_INGREDIENT, @@ -15,17 +15,41 @@ import { ADD_BUN, CHANGE_INGREDIENT_ORDER, SET_ACTIVE_TAB - } from '../../utils/constants'; +} from '../../utils/constants'; +import { TIngredientsByType, TIngredientsById, TIngredient } from '../types/ingredients'; +import { TIngredientsAction } from '../types/ingredients'; + + +type TAddedIngredients = { + bun: TIngredient | null; + others: Array | []; +} + +export type TMenuState = { + ingredientsByType: TIngredientsByType; + ingredientsById: TIngredientsById; + ingredientsRequest: boolean; + ingredientsFailed: boolean; + + addedIngredients: TAddedIngredients; + currentIngredient: null | TIngredient; + + activeTab: string; + + orderId: number | null; + orderRequest: boolean; + orderFailed: boolean; +} const initialMenuState = { - ingredientsByType: null, - ingredientsById: null, + ingredientsByType: {}, + ingredientsById: {}, ingredientsRequest: false, ingredientsFailed: false, addedIngredients: { bun: null, - others: [] + others: [], }, currentIngredient: null, @@ -37,7 +61,8 @@ const initialMenuState = { } let uniqId = 0; -export const ingredientsReducer = (state = initialMenuState, action) => { + +export const ingredientsReducer = (state: TMenuState = initialMenuState, action: TIngredientsAction): TMenuState => { switch (action.type) { case GET_INGREDIENTS_REQUEST: { return { @@ -61,14 +86,14 @@ export const ingredientsReducer = (state = initialMenuState, action) => { ingredientsFailed: true } } - case GET_ORDER_REQUEST: { + case SEND_ORDER_REQUEST: { return { ...state, orderRequest: true, orderFailed: false } } - case GET_ORDER_SUCCESS: { + case SEND_ORDER_SUCCESS: { return { ...state, orderRequest: false, @@ -79,7 +104,7 @@ export const ingredientsReducer = (state = initialMenuState, action) => { } } } - case GET_ORDER_FAILED: { + case SEND_ORDER_FAILED: { return { ...state, orderRequest: false, @@ -129,10 +154,10 @@ export const ingredientsReducer = (state = initialMenuState, action) => { } case CHANGE_INGREDIENT_ORDER: { const items = state.addedIngredients.others.slice(); - const draggedItem = items.splice(action.prevId, 1)[0]; + const draggedItem = items.splice(action.prevIndex, 1)[0]; items.splice( - action.newId, + action.newIndex, 0, draggedItem ); diff --git a/src/services/reducers/user.js b/src/services/reducers/user.tsx similarity index 85% rename from src/services/reducers/user.js rename to src/services/reducers/user.tsx index 17d27df..ebb0091 100644 --- a/src/services/reducers/user.js +++ b/src/services/reducers/user.tsx @@ -25,11 +25,37 @@ import { RESET_PASSWORD_FAILED, LOGOUT_USER_SUCCESS, - LOGOUT_USER_FAILED, + LOGOUT_USER_FAILED +} from '../../utils/constants'; +import { TUserActions } from '../types/user'; - GET_USER_ORDERS - } from '../../utils/constants'; +export type TUserState = { + name: string, + email: string, + password: string, + prevSavedName: string, + prevSavedEmail: string, + prevSavedPassword: string, + isAuthSuccess: boolean, + isUserLoaded: boolean, + + addUserRequest: boolean, + addUserFailed: boolean, + + loginUserRequest: boolean, + loginUserFailed: boolean, + loginErrorMessage: string, + + logoutError: boolean, + + editUserFailed: boolean, + isUserEdited: boolean, + + canResetPassword: boolean, + resetPasswordSuccess: boolean, + code: string +} const initialUserState = { name: '', @@ -56,12 +82,10 @@ const initialUserState = { canResetPassword: false, resetPasswordSuccess: false, - code: '', - - orders: [] + code: '' } -export const userReducer = (state = initialUserState, action) => { +export const userReducer = (state: TUserState = initialUserState, action: TUserActions) => { switch (action.type) { case SET_REGISTER_FORM_VALUE: { @@ -218,17 +242,6 @@ export const userReducer = (state = initialUserState, action) => { logoutError: true } } - - - - - case GET_USER_ORDERS: { - return { - ...state, - orders: action.action.orders - } - } - default: return state; } diff --git a/src/services/reducers/ws.js b/src/services/reducers/ws.js deleted file mode 100644 index bce4041..0000000 --- a/src/services/reducers/ws.js +++ /dev/null @@ -1,31 +0,0 @@ -import { WS_STATUS, WS_MESSAGE } from '../../utils/constants'; -import { wsClose, wsConnecting, wsError, wsOpen } from '../actions/ws'; -import { createReducer } from '@reduxjs/toolkit'; - -const initialWSState = { - status: WS_STATUS.OFFLINE, - connectionError: '', - feed: {} -} - -export const wsReducer = createReducer(initialWSState, (builder) => { - - builder - .addCase(wsConnecting, (state) => { - state.status = WS_STATUS.CONNECTING - }) - .addCase(wsOpen, (state) => { - state.status = WS_STATUS.ONLINE; - state.connectionError = '' - }) - .addCase(wsClose, (state) => { - state.status = WS_STATUS.OFFLINE; - state.connectionError = '' - }) - .addCase(wsError, (state, action) => { - state.connectionError = action.payload - }) - .addCase(WS_MESSAGE, (state, action) => { - state.feed = action.payload; - }) -}) diff --git a/src/services/reducers/ws.tsx b/src/services/reducers/ws.tsx new file mode 100644 index 0000000..34c9276 --- /dev/null +++ b/src/services/reducers/ws.tsx @@ -0,0 +1,50 @@ +import { WS_STATUS_OFFLINE, WS_STATUS_CONNECTING, WS_STATUS_ONLINE, WS_MESSAGE } from '../../utils/constants'; +import { wsClose, wsConnecting, wsError, wsOpen } from '../actions/ws'; +import { createReducer } from '@reduxjs/toolkit'; +import { TWSMessage, IWSMessageAction, IWSErrorAction } from '../types/ws'; +import { TFeedData } from '../types/order'; + +export type TWSState = { + status: typeof WS_STATUS_ONLINE | typeof WS_STATUS_CONNECTING | typeof WS_STATUS_OFFLINE; + feed?: TFeedData | TWSMessage | undefined; + connectionError: string; +}; + +const initialWSState: TWSState = { + status: WS_STATUS_OFFLINE, + connectionError: '', + feed: undefined +} + +export const wsReducer = createReducer(initialWSState, (builder) => { + + builder + .addCase(wsConnecting, (state: TWSState) => { + state.status = WS_STATUS_CONNECTING + }) + .addCase(wsOpen, (state: TWSState) => { + state.status = WS_STATUS_ONLINE; + state.connectionError = '' + }) + .addCase(wsClose, (state: TWSState) => { + state.status = WS_STATUS_OFFLINE; + state.connectionError = '' + }) + .addCase(wsError, (state: TWSState, action: IWSErrorAction) => { + let error = ''; + if (typeof action.payload !== 'string' && typeof action.payload !== 'undefined') { + if (!action.payload.success) { + error = action.payload.message; + } + } + + state.connectionError = error; + }) + .addCase(WS_MESSAGE, (state: TWSState, action: IWSMessageAction) => { + let feed = undefined; + if (typeof action.payload !== 'string') { + feed = action.payload; + } + state.feed = feed; + }) +}) diff --git a/src/services/store.tsx b/src/services/store.tsx new file mode 100644 index 0000000..2a10bdb --- /dev/null +++ b/src/services/store.tsx @@ -0,0 +1,24 @@ +import { compose, createStore, applyMiddleware } from 'redux'; +import { socketMiddleware } from './middlewares/ws'; +import thunk from 'redux-thunk'; +import { rootReducer } from './reducers'; +import { wsConnect, wsDisconnect, wsConnecting, wsOpen, wsClose, wsError, wsMessage } from './actions/ws'; + +const wsActions = { + wsConnect, + wsDisconnect, + wsConnecting, + onOpen: wsOpen, + onClose: wsClose, + onError: wsError, + onMessage: wsMessage +} + +const composeEnhancers = + typeof window === 'object' && (window as any)['__REDUX_DEVTOOLS_EXTENSION_COMPOSE__'] + ? (window as any)['__REDUX_DEVTOOLS_EXTENSION_COMPOSE__']({}) + : compose; + +const enhancer = composeEnhancers(applyMiddleware(thunk, socketMiddleware(wsActions))); + +export const store = createStore(rootReducer, enhancer); diff --git a/src/services/types/index.tsx b/src/services/types/index.tsx new file mode 100644 index 0000000..ffe68a3 --- /dev/null +++ b/src/services/types/index.tsx @@ -0,0 +1,25 @@ +import { ThunkAction } from 'redux-thunk'; +import { Action, ActionCreator } from 'redux'; +import { store } from '../store'; +import { TIngredientsAction } from '../types/ingredients'; +import { TUserActions } from './user'; +import { TWSAction } from '../types/ws'; +import { TMenuState } from '../reducers'; +import { TUserState } from '../reducers/user'; +import { TWSState } from '../reducers/ws'; + +// export type TRootState = ReturnType +export type TRootState = { + menu: TMenuState; + user: TUserState; + ws: TWSState; +}; + +// Типизация всех экшенов приложения +type TApplicationActions = TIngredientsAction | TUserActions | TWSAction; + +export type AppDispatch = typeof store.dispatch; + +export type AppThunk = ActionCreator< + ThunkAction +>; \ No newline at end of file diff --git a/src/services/types/ingredients.tsx b/src/services/types/ingredients.tsx new file mode 100644 index 0000000..dd217c2 --- /dev/null +++ b/src/services/types/ingredients.tsx @@ -0,0 +1,150 @@ +import { + GET_INGREDIENTS_REQUEST, + GET_INGREDIENTS_FAILED, + GET_INGREDIENTS_SUCCESS, + SEND_ORDER_REQUEST, + SEND_ORDER_FAILED, + SEND_ORDER_SUCCESS, + SHOW_INGREDIENT, + HIDE_INGREDIENT, + ADD_INGREDIENT, + DELETE_INGREDIENT, + ADD_BUN, + CHANGE_INGREDIENT_ORDER, + SET_ACTIVE_TAB +} from '../../utils/constants'; + +export type TIngredient = { + _id: string; + name: string; + type: string; + proteins: number; + fat: number; + carbohydrates: number; + calories: number; + price: number; + image: string; + image_mobile: string; + image_large: string; + __v?: number; + key?: number; +}; + +export type TIngredientsByType = { + [name: string]: Array; +} + +export type TIngredientsById = { + [name: string] : TIngredient; +} + + +// Типизация получения списка ингридиентов +interface IGetIngredientsAction { + readonly type: typeof GET_INGREDIENTS_REQUEST; +} + +interface IGetIngredientsFailedAction { + readonly type: typeof GET_INGREDIENTS_FAILED; +} + +interface IGetCountriesSuccessAction { + readonly type: typeof GET_INGREDIENTS_SUCCESS; + readonly ingredientsByType: TIngredientsByType; + readonly ingredientsById: TIngredientsById; +} + +export const getIngredientsAction = (): IGetIngredientsAction => ({ + type: GET_INGREDIENTS_REQUEST +}); + +export const getIngredientsSuccessAction = +(ingredientsByType: TIngredientsByType, ingredientsById: TIngredientsById): IGetCountriesSuccessAction => ({ + type: GET_INGREDIENTS_SUCCESS, + ingredientsByType, + ingredientsById +}); + +export const getIngredientsFailedAction = (): IGetIngredientsFailedAction => ({ + type: GET_INGREDIENTS_FAILED +}); + +// Типизация создания заказа +interface ISendOrderAction { + readonly type: typeof SEND_ORDER_REQUEST; +} + +interface ISendOrderFailedAction { + readonly type: typeof SEND_ORDER_FAILED; +} + +interface ISendOrderSuccessAction { + readonly type: typeof SEND_ORDER_SUCCESS; + readonly orderId: number; +} + +export const sendOrderAction = (): ISendOrderAction => ({ + type: SEND_ORDER_REQUEST +}); + +export const sendOrderSuccessAction = (orderId: number): ISendOrderSuccessAction => ({ + type: SEND_ORDER_SUCCESS, + orderId +}); + +export const sendOrderFailedAction = (): ISendOrderFailedAction => ({ + type: SEND_ORDER_FAILED +}); + +// Типизация других действий с ингридиентами +interface IShowIngredient { + readonly type: typeof SHOW_INGREDIENT; + id: string; +} + +interface IHideIngredient { + readonly type: typeof HIDE_INGREDIENT; +} + +interface IAddIngredient { + readonly type: typeof ADD_INGREDIENT; + id: string; +} + +interface IDeleteIngredient { + readonly type: typeof DELETE_INGREDIENT; + index: number; +} + +interface IAddBun { + readonly type: typeof ADD_BUN; + id: string; +} + +interface IChangeOrderIngredients { + readonly type: typeof CHANGE_INGREDIENT_ORDER; + prevIndex: number; + newIndex: number; +} + +interface ISetActiveTab { + readonly type: typeof SET_ACTIVE_TAB; + name: string; +} + +export type TIngredientsAction = + IGetIngredientsAction + | IGetCountriesSuccessAction + | IGetIngredientsFailedAction + | ISendOrderAction + | ISendOrderSuccessAction + | ISendOrderFailedAction + | IShowIngredient + | IHideIngredient + | IAddIngredient + | IDeleteIngredient + | IAddBun + | IChangeOrderIngredients + | ISetActiveTab; + +export type TIngredientList = {id: string, index: number}; diff --git a/src/services/types/order.tsx b/src/services/types/order.tsx new file mode 100644 index 0000000..56cdc6e --- /dev/null +++ b/src/services/types/order.tsx @@ -0,0 +1,17 @@ +export type TOrder = { + _id: string; + status: 'created' | 'done' | 'progress' | 'cancelled'; + name: string; + createdAt: Date | string; + updatedAt: Date | string; + number: number; + ingredients: Array +} + +export type TFeedData = { + success: boolean; + total: number; + totalToday: number; + orders: Array; +} + diff --git a/src/services/types/request.tsx b/src/services/types/request.tsx new file mode 100644 index 0000000..bdb5774 --- /dev/null +++ b/src/services/types/request.tsx @@ -0,0 +1,6 @@ +export type TRequestRetryOnSuccess = { + [name: string]: string; +} + +export type requestRetryOnFail = (error: any) => void; +export type requestRetryOnSuccess = (res: any) => void; diff --git a/src/services/types/user.tsx b/src/services/types/user.tsx new file mode 100644 index 0000000..f13500a --- /dev/null +++ b/src/services/types/user.tsx @@ -0,0 +1,223 @@ +import { + ADD_USER_REQUEST, + ADD_USER_SUCCESS, + ADD_USER_FAILED, + + LOGIN_USER_REQUEST, + LOGIN_USER_SUCCESS, + LOGIN_USER_FAILED, + + GET_USER_SUCCESS, + GET_USER_FAILED, + + EDIT_USER_SUCCESS, + EDIT_USER_FAILED, + + FORGOT_PASSWORD_SUCCESS, + FORGOT_PASSWORD_FAILED, + + RESET_PASSWORD_SUCCESS, + RESET_PASSWORD_FAILED, + + LOGOUT_USER_SUCCESS, + LOGOUT_USER_FAILED, + + SET_REGISTER_FORM_VALUE, + SET_EDIT_USER_FORM, + RESET_EDIT_USER_FORM + } from '../../utils/constants'; + +export type TUser = { + name: string; + email: string; + password?: string; +}; + +// Типизация экшнов для создания нового пользователя +interface IAddUserAction { + readonly type: typeof ADD_USER_REQUEST; +} + +interface IAddUserSuccessAction { + readonly type: typeof ADD_USER_SUCCESS; + user: TUser +} + +interface IAddUserFailedAction { + readonly type: typeof ADD_USER_FAILED; +} + +export const addUserAction = (): IAddUserAction => ({ + type: ADD_USER_REQUEST +}); + +export const addUserSuccessAction = (user: TUser): IAddUserSuccessAction => ({ + type: ADD_USER_SUCCESS, + user +}); + +export const addUserFailedAction = (): IAddUserFailedAction => ({ + type: ADD_USER_FAILED +}); + +// Типизация экшнов для логина пользователя +interface ILoginUserAction { + readonly type: typeof LOGIN_USER_REQUEST; +} + +interface ILoginUserSuccessAction { + readonly type: typeof LOGIN_USER_SUCCESS; + user: TUser +} + +interface ILoginUserFailedAction { + readonly type: typeof LOGIN_USER_FAILED; + readonly errorMessage: string; +} + +export const loginUserAction = (): ILoginUserAction => ({ + type: LOGIN_USER_REQUEST +}); + +export const loginUserSuccessAction = (user: TUser): ILoginUserSuccessAction => ({ + type: LOGIN_USER_SUCCESS, + user +}); + +export const loginUserFailedAction = (errorMessage: string): ILoginUserFailedAction => ({ + type: LOGIN_USER_FAILED, + errorMessage +}); + +// Типизация экшнов получения данных о пользователе +interface IGetUserSuccessAction { + readonly type: typeof GET_USER_SUCCESS; + user: TUser +} + +interface IGetUserFailedAction { + readonly type: typeof GET_USER_FAILED; +} + +export const getUserSuccessAction = (user: TUser): IGetUserSuccessAction => ({ + type: GET_USER_SUCCESS, + user +}); + +export const getUserFailedAction = (): IGetUserFailedAction => ({ + type: GET_USER_FAILED +}); + +// Типизация экшнов редактирования данных о пользователе +interface IEditUserSuccessAction { + readonly type: typeof EDIT_USER_SUCCESS; + user: TUser +} + +interface IEditUserFailedAction { + readonly type: typeof EDIT_USER_FAILED; +} + +export const editUserSuccessAction = (user: TUser): IEditUserSuccessAction => ({ + type: EDIT_USER_SUCCESS, + user +}); + +export const editUserFailedAction = (): IEditUserFailedAction => ({ + type: EDIT_USER_FAILED +}); + +// Типизация экшнов для забытого юзером пароля +interface IForgotPasswordSuccessAction { + readonly type: typeof FORGOT_PASSWORD_SUCCESS; +} + +interface IForgotPasswordFailedAction { + readonly type: typeof FORGOT_PASSWORD_FAILED; +} + +export const forgotPasswordSuccessAction = (): IForgotPasswordSuccessAction => ({ + type: FORGOT_PASSWORD_SUCCESS +}); + +export const forgotPasswordFailedAction = (): IForgotPasswordFailedAction => ({ + type: FORGOT_PASSWORD_FAILED +}); + + +// Типизация экшнов для сброса пароля +interface IResetPasswordSuccessAction { + readonly type: typeof RESET_PASSWORD_SUCCESS; +} + +interface IResetPasswordFailedAction { + readonly type: typeof RESET_PASSWORD_FAILED; +} + +export const resetPasswordSuccessAction = (): IResetPasswordSuccessAction => ({ + type: RESET_PASSWORD_SUCCESS +}); + +export const resetPasswordFailedAction = (): IResetPasswordFailedAction => ({ + type: RESET_PASSWORD_FAILED +}); + +// Типизация экшнов для логаута пользователя +interface ILogoutUserSuccessAction { + readonly type: typeof LOGOUT_USER_SUCCESS; +} + +interface ILogoutUserFailedAction { + readonly type: typeof LOGOUT_USER_FAILED; + readonly errorMessage: string; +} + +export const logoutUserSuccessAction = (): ILogoutUserSuccessAction => ({ + type: LOGOUT_USER_SUCCESS +}); + +export const logoutUserFailedAction = (errorMessage: string): ILogoutUserFailedAction => ({ + type: LOGOUT_USER_FAILED, + errorMessage +}); + +// Типизация экшнов работы с формами +interface ISetRegisterFormAction { + readonly type: typeof SET_REGISTER_FORM_VALUE; + [name: string]: string; +} + +type TSetEditFormAction = { + readonly type: typeof SET_EDIT_USER_FORM; + [name: string]: string; +} & { + isUserEdited: boolean; +} + +interface IResetEditUserFormUserAction { + readonly type: typeof RESET_EDIT_USER_FORM; + name: string; + email: string; + password: string; +} + +export type TUserActions = + IAddUserAction + | IAddUserSuccessAction + | IAddUserFailedAction + | ILoginUserAction + | ILoginUserSuccessAction + | ILoginUserFailedAction + | IGetUserSuccessAction + | IGetUserFailedAction + | IEditUserSuccessAction + | IEditUserFailedAction + | IForgotPasswordSuccessAction + | IForgotPasswordFailedAction + | IResetPasswordSuccessAction + | IResetPasswordFailedAction + | ILogoutUserSuccessAction + | ILogoutUserFailedAction + | ISetRegisterFormAction + | TSetEditFormAction + | IResetEditUserFormUserAction; diff --git a/src/services/types/ws.tsx b/src/services/types/ws.tsx new file mode 100644 index 0000000..3b41ccd --- /dev/null +++ b/src/services/types/ws.tsx @@ -0,0 +1,72 @@ +import { + WS_MESSAGE, + WS_CONNECT, + WS_DISCONNECT, + WS_CONNECTING, + WS_OPEN, + WS_CLOSE, + WS_ERROR +} from '../../utils/constants'; +import { TOrder } from './order'; + +export type TWSErrorMessage = { + success: false; + message: string; +} + +export type TWSMessage = { + success: true; + orders: Array; +} | TWSErrorMessage; + + +type TPayload = TWSMessage | TWSErrorMessage | string| undefined; + +export interface IWSMessageAction { + readonly type: typeof WS_MESSAGE; + payload: TPayload; +} + +export interface IWSErrorAction { + readonly type: typeof WS_ERROR; + payload: TPayload; +} + +interface IWSOpenAction { + readonly type: typeof WS_OPEN; + payload: TPayload; +} + +interface IWSCloseAction { + readonly type: typeof WS_CLOSE; + payload: TPayload; +} + +interface IWSConnectingAction { + readonly type: typeof WS_CONNECTING; + payload: TPayload; +} + +export interface IWSConnectAction { + readonly type: typeof WS_CONNECT; + payload: TPayload; +} + +interface IWSDisconnectAction { + readonly type: typeof WS_DISCONNECT; + payload: TPayload; +} + +export const wsMessageAction = (payload: TWSMessage): IWSMessageAction => ({ + type: WS_MESSAGE, + payload +}); + +export type TWSAction = + IWSMessageAction + | IWSErrorAction + | IWSOpenAction + | IWSCloseAction + | IWSConnectingAction + | IWSConnectAction + | IWSDisconnectAction; diff --git a/src/utils/api.js b/src/utils/api.tsx similarity index 56% rename from src/utils/api.js rename to src/utils/api.tsx index 862d10a..0e77ee9 100644 --- a/src/utils/api.js +++ b/src/utils/api.tsx @@ -9,21 +9,70 @@ import { PASSWORDFORGOTURL, PASSWORDRESETURL } from "./constants"; +import { TUser } from "../services/types/user"; +import { TIngredient } from "../services/types/ingredients"; +import { TOrder } from "../services/types/order"; + +type TErrorResponse = { success: false; message: string; }; +type TIngredientsResponse = { success: true; data: Array; } | TErrorResponse; +type TSendOrderResponse = { + success: true; + name: string; + order: TOrder; +} | TErrorResponse; + +type TUserResponse = { + success: true; + user: TUser +} | TErrorResponse; + +type TLoginUserResponse = { + success: true; + accessToken: string; + refreshToken: string; + user: TUser; +} | TErrorResponse; + +type TLogoutUserResponse = { + success: true; + message: string; +} | TErrorResponse; + +type TRefreshTokenResponse = { + success: true; + accessToken: string; + refreshToken: string; +} | TErrorResponse; + +type TForgotPasswordResponse = { + success: boolean; + message: string; + user: TUser; +} + +export type TResponse = +TIngredientsResponse +& TSendOrderResponse +& TUserResponse +& TLoginUserResponse +& TLogoutUserResponse +& TRefreshTokenResponse +& TForgotPasswordResponse; -const checkResponse = async (data) => { +const checkResponse = async (data: Response): Promise => { if (!data.ok) { - throw new Error(data.message, { cause: await data.json() }); + throw new Error(data.status.toString(), { cause: await data.json() }); } return data.json(); } -const getIngredientsRequest = async () => { +const getIngredientsRequest = async (): Promise => { const res = await fetch(GETINGREDIENTSURL); return await checkResponse(res); } -const sendOrderRequest = async (ingredientIds, accessToken) => { +const sendOrderRequest = async (ingredientIds: Array, accessToken: string): Promise => { const res = await fetch(SAVEORDERURL, { method: 'POST', headers: { @@ -38,7 +87,7 @@ const sendOrderRequest = async (ingredientIds, accessToken) => { return await checkResponse(res); } -const getUserRequest = async ({accessToken}) => { +const getUserRequest = async (accessToken: string): Promise => { const res = await fetch(USERURL, { method: 'GET', headers: { @@ -50,7 +99,7 @@ const getUserRequest = async ({accessToken}) => { return await checkResponse(res); } -const editUserRequest = async ({user, accessToken}) => { +const editUserRequest = async (accessToken: string, user: TUser): Promise => { const res = await fetch(USERURL, { method: 'PATCH', headers: { @@ -63,7 +112,7 @@ const editUserRequest = async ({user, accessToken}) => { return checkResponse(res); } -const addUserRequest = async (user) => { +const addUserRequest = async (user: TUser): Promise => { const res = await fetch(ADDUSERURL, { method: 'POST', headers: { @@ -75,7 +124,7 @@ const addUserRequest = async (user) => { return await checkResponse(res); } -const loginRequest = async (user) => { +const loginRequest = async (user: TUser): Promise => { const res = await fetch(LOGINUSERURL, { method: 'POST', headers: { @@ -87,7 +136,7 @@ const loginRequest = async (user) => { return await checkResponse(res); } -const logoutRequest = async ({token}) => { +const logoutRequest = async (accessToken: string, token: string): Promise => { const res = await fetch(LOGOUTUSERURL, { method: 'POST', headers: { @@ -99,7 +148,7 @@ const logoutRequest = async ({token}) => { return await checkResponse(res); } -const refreshTokenRequest = async (token) => { +const refreshTokenRequest = async (token: string): Promise => { const res = await fetch(REFRESHTOKENURL, { method: 'POST', headers: { @@ -111,7 +160,7 @@ const refreshTokenRequest = async (token) => { return await checkResponse(res); } -const passwordForgotRequest = async (email) => { +const passwordForgotRequest = async (email: string): Promise => { const res = await fetch(PASSWORDFORGOTURL, { method: 'POST', headers: { @@ -123,7 +172,7 @@ const passwordForgotRequest = async (email) => { return await checkResponse(res); } -const passwordResetRequest = async (password, token) => { +const passwordResetRequest = async (password: string, token: string): Promise => { const res = await fetch(PASSWORDRESETURL, { method: 'POST', headers: { diff --git a/src/utils/constants.js b/src/utils/constants.js deleted file mode 100644 index 982d55e..0000000 --- a/src/utils/constants.js +++ /dev/null @@ -1,81 +0,0 @@ -const BASEURL = 'https://norma.nomoreparties.space/api'; -export const GETINGREDIENTSURL = BASEURL + '/ingredients'; -export const SAVEORDERURL = BASEURL + '/orders'; -export const ADDUSERURL = BASEURL + '/auth/register'; -export const LOGINUSERURL = BASEURL + '/auth/login'; -export const LOGOUTUSERURL = BASEURL + '/auth/logout'; -export const REFRESHTOKENURL = BASEURL + '/auth/token'; -export const USERURL = BASEURL + '/auth/user'; -export const PASSWORDFORGOTURL = BASEURL + '/password-reset'; -export const PASSWORDRESETURL = BASEURL + '/password-reset/reset'; - -export const WSURL = 'wss://norma.nomoreparties.space'; -export const GET_ALL_ORDERS_URL = WSURL + '/orders/all'; -export const GET_USER_ORDERS_URL = WSURL + '/orders'; -export const WS_STATUS = { - CONNECTING : 'CONNECTING...', - ONLINE : 'ONLINE', - OFFLINE : 'OFFLINE' -} -export const WS_MESSAGE = 'WS_MESSAGE'; - -// Константы для обработки запроса для получения всех ингридиентов -export const GET_INGREDIENTS_REQUEST = 'GET_INGREDIENTS_REQUEST'; -export const GET_INGREDIENTS_SUCCESS = 'GET_INGREDIENTS_SUCCESS'; -export const GET_INGREDIENTS_FAILED = 'GET_INGREDIENTS_FAILED'; - -// Константы для обработки запроса оформления заказа -export const GET_ORDER_REQUEST = 'GET_ORDER_REQUEST'; -export const GET_ORDER_SUCCESS = 'GET_ORDER_SUCCESS'; -export const GET_ORDER_FAILED = 'GET_ORDER_FAILED'; - -// Константы для получения/удаления данных об отдельном ингридиенте -export const SHOW_INGREDIENT = 'SHOW_INGREDIENT'; -export const HIDE_INGREDIENT = 'HIDE_INGREDIENT'; - -// Константы для добавления ингридиента в конструктор бургера -export const ADD_INGREDIENT = 'ADD_INGREDIENT'; -export const DELETE_INGREDIENT = 'DELETE_INGREDIENT'; -export const ADD_BUN = 'ADD_BUN'; - -// Константы для сортировки ингридиентов бургера -export const CHANGE_INGREDIENT_ORDER = 'CHANGE_INGREDIENT_ORDER'; - -// Константа для изменения активного таба -export const SET_ACTIVE_TAB = 'SET_ACTIVE_TAB'; - -// Константы для обработки запроса получения данных о пользователе -export const SET_REGISTER_FORM_VALUE = 'SET_REGISTER_FORM_VALUE'; -export const SET_EDIT_USER_FORM = 'SET_EDIT_USER_FORM'; - -export const ADD_USER_REQUEST = 'ADD_USER_REQUEST'; -export const ADD_USER_SUCCESS = 'ADD_USER_SUCCESS'; -export const ADD_USER_FAILED = 'ADD_USER_FAILED'; - -export const LOGIN_USER_REQUEST = 'LOGIN_USER_REQUEST'; -export const LOGIN_USER_SUCCESS = 'LOGIN_USER_SUCCESS'; -export const LOGIN_USER_FAILED = 'LOGIN_USER_FAILED'; - -export const LOGOUT_USER_SUCCESS = 'LOGOUT_USER_SUCCESS'; -export const LOGOUT_USER_FAILED = 'LOGOUT_USER_FAILED'; - -export const GET_USER_SUCCESS = 'GET_USER_SUCCESS'; -export const GET_USER_FAILED = 'GET_USER_FAILED'; - -export const EDIT_USER_SUCCESS = 'EDIT_USER_SUCCESS'; -export const EDIT_USER_FAILED = 'EDIT_USER_FAILED'; -export const RESET_EDIT_USER_FORM = 'RESET_EDIT_USER_FORM'; - -export const FORGOT_PASSWORD_SUCCESS = 'FORGOT_PASSWORD_SUCCESS'; -export const FORGOT_PASSWORD_FAILED = 'FORGOT_PASSWORD_FAILED'; - -export const RESET_PASSWORD_SUCCESS = 'RESET_PASSWORD_SUCCESS'; -export const RESET_PASSWORD_FAILED = 'RESET_PASSWORD_FAILED'; - -export const GET_USER_ORDERS = 'GET_USER_ORDERS'; - -export const ORDER_STATUSES = { - done: 'Выполнен', - cancelled: 'Отменён', - progress: 'Готовится' -} diff --git a/src/utils/constants.tsx b/src/utils/constants.tsx new file mode 100644 index 0000000..cab95c3 --- /dev/null +++ b/src/utils/constants.tsx @@ -0,0 +1,87 @@ +const BASEURL = 'https://norma.nomoreparties.space/api'; +export const GETINGREDIENTSURL = BASEURL + '/ingredients'; +export const SAVEORDERURL = BASEURL + '/orders'; +export const ADDUSERURL = BASEURL + '/auth/register'; +export const LOGINUSERURL = BASEURL + '/auth/login'; +export const LOGOUTUSERURL = BASEURL + '/auth/logout'; +export const REFRESHTOKENURL = BASEURL + '/auth/token'; +export const USERURL = BASEURL + '/auth/user'; +export const PASSWORDFORGOTURL = BASEURL + '/password-reset'; +export const PASSWORDRESETURL = BASEURL + '/password-reset/reset'; + +export const WSURL = 'wss://norma.nomoreparties.space'; +export const GET_ALL_ORDERS_URL = WSURL + '/orders/all'; +export const GET_USER_ORDERS_URL = WSURL + '/orders'; +export const WS_STATUS_OFFLINE = 'OFFLINE'; +export const WS_STATUS_ONLINE = 'ONLINE'; +export const WS_STATUS_CONNECTING = 'CONNECTING...'; + +export const WS_MESSAGE: 'WS_MESSAGE' = 'WS_MESSAGE'; +export const WS_CONNECT: 'WS_CONNECT' = 'WS_CONNECT'; +export const WS_DISCONNECT: 'WS_DISCONNECT' = 'WS_DISCONNECT'; +export const WS_CONNECTING: 'WS_CONNECTING' = 'WS_CONNECTING'; +export const WS_OPEN: 'WS_OPEN' = 'WS_OPEN'; +export const WS_CLOSE: 'WS_CLOSE' = 'WS_CLOSE'; +export const WS_ERROR: 'WS_ERROR' = 'WS_ERROR'; + +// Константы для обработки запроса для получения всех ингридиентов +export const GET_INGREDIENTS_REQUEST: 'GET_INGREDIENTS_REQUEST' = 'GET_INGREDIENTS_REQUEST'; +export const GET_INGREDIENTS_SUCCESS: 'GET_INGREDIENTS_SUCCESS' = 'GET_INGREDIENTS_SUCCESS'; +export const GET_INGREDIENTS_FAILED: 'GET_INGREDIENTS_FAILED' = 'GET_INGREDIENTS_FAILED'; + +// Константы для обработки запроса оформления заказа +export const SEND_ORDER_REQUEST: 'SEND_ORDER_REQUEST'= 'SEND_ORDER_REQUEST'; +export const SEND_ORDER_SUCCESS: 'SEND_ORDER_SUCCESS' = 'SEND_ORDER_SUCCESS'; +export const SEND_ORDER_FAILED: 'SEND_ORDER_FAILED' = 'SEND_ORDER_FAILED'; + +// Константы для получения/удаления данных об отдельном ингридиенте +export const SHOW_INGREDIENT: 'SHOW_INGREDIENT' = 'SHOW_INGREDIENT'; +export const HIDE_INGREDIENT: 'HIDE_INGREDIENT' = 'HIDE_INGREDIENT'; + +// Константы для добавления ингридиента в конструктор бургера +export const ADD_INGREDIENT: 'ADD_INGREDIENT' = 'ADD_INGREDIENT'; +export const DELETE_INGREDIENT: 'DELETE_INGREDIENT' = 'DELETE_INGREDIENT'; +export const ADD_BUN: 'ADD_BUN' = 'ADD_BUN'; + +// Константы для сортировки ингридиентов бургера +export const CHANGE_INGREDIENT_ORDER: 'CHANGE_INGREDIENT_ORDER' = 'CHANGE_INGREDIENT_ORDER'; + +// Константа для изменения активного таба +export const SET_ACTIVE_TAB: 'SET_ACTIVE_TAB' = 'SET_ACTIVE_TAB'; + +// Константы для обработки запроса получения данных о пользователе +export const SET_REGISTER_FORM_VALUE: 'SET_REGISTER_FORM_VALUE' = 'SET_REGISTER_FORM_VALUE'; +export const SET_EDIT_USER_FORM: 'SET_EDIT_USER_FORM' = 'SET_EDIT_USER_FORM'; + +export const ADD_USER_REQUEST: 'ADD_USER_REQUEST' = 'ADD_USER_REQUEST'; +export const ADD_USER_SUCCESS: 'ADD_USER_SUCCESS' = 'ADD_USER_SUCCESS'; +export const ADD_USER_FAILED: 'ADD_USER_FAILED' = 'ADD_USER_FAILED'; + +export const LOGIN_USER_REQUEST: 'LOGIN_USER_REQUEST' = 'LOGIN_USER_REQUEST'; +export const LOGIN_USER_SUCCESS: 'LOGIN_USER_SUCCESS' = 'LOGIN_USER_SUCCESS'; +export const LOGIN_USER_FAILED: 'LOGIN_USER_FAILED' = 'LOGIN_USER_FAILED'; + +export const LOGOUT_USER_SUCCESS: 'LOGOUT_USER_SUCCESS' = 'LOGOUT_USER_SUCCESS'; +export const LOGOUT_USER_FAILED: 'LOGOUT_USER_FAILED' = 'LOGOUT_USER_FAILED'; + +export const GET_USER_SUCCESS: 'GET_USER_SUCCESS' = 'GET_USER_SUCCESS'; +export const GET_USER_FAILED: 'GET_USER_FAILED' = 'GET_USER_FAILED'; + +export const EDIT_USER_SUCCESS: 'EDIT_USER_SUCCESS' = 'EDIT_USER_SUCCESS'; +export const EDIT_USER_FAILED: 'EDIT_USER_FAILED' = 'EDIT_USER_FAILED'; +export const RESET_EDIT_USER_FORM: 'RESET_EDIT_USER_FORM' = 'RESET_EDIT_USER_FORM'; + +export const FORGOT_PASSWORD_SUCCESS: 'FORGOT_PASSWORD_SUCCESS' = 'FORGOT_PASSWORD_SUCCESS'; +export const FORGOT_PASSWORD_FAILED: 'FORGOT_PASSWORD_FAILED' = 'FORGOT_PASSWORD_FAILED'; + +export const RESET_PASSWORD_SUCCESS: 'RESET_PASSWORD_SUCCESS' = 'RESET_PASSWORD_SUCCESS'; +export const RESET_PASSWORD_FAILED: 'RESET_PASSWORD_FAILED' = 'RESET_PASSWORD_FAILED'; + +export const GET_USER_ORDERS: 'GET_USER_ORDERS' = 'GET_USER_ORDERS'; + +export const ORDER_STATUSES = { + done: 'Выполнен', + cancelled: 'Отменён', + progress: 'Готовится', + created: 'Создан' +} diff --git a/src/utils/cookie.js b/src/utils/cookie.tsx similarity index 52% rename from src/utils/cookie.js rename to src/utils/cookie.tsx index 0aff85e..4515ff7 100644 --- a/src/utils/cookie.js +++ b/src/utils/cookie.tsx @@ -1,4 +1,15 @@ -export const setCookie = (name, value, props) => { +type TCookieProps = { + path?: string; + expires?: Date | string | number; + domain?: string; + httpOnly?: boolean; + sameSite?: boolean; + secure?: boolean; + overwrite?: boolean; + maxAge?: number; +} + +export const setCookie = (name: string, value: string, props?: TCookieProps) => { props = { path: '/', ...props @@ -12,14 +23,16 @@ export const setCookie = (name, value, props) => { exp = props.expires = d; } - if (exp && exp.toUTCString) { + if (exp && exp instanceof Date) { props.expires = exp.toUTCString(); } value = encodeURIComponent(value); let updatedCookie = name + '=' + value; - for (const propName in props) { + let propName: keyof TCookieProps; + + for (propName in props) { updatedCookie += '; ' + propName; const propValue = props[propName]; if (propValue !== true) { @@ -30,9 +43,10 @@ export const setCookie = (name, value, props) => { document.cookie = updatedCookie; } -export const getCookie = (name) => { - let cookies = document.cookie.split('; ').reduce((res, el) => { - let cookie = el.split('='); +export const getCookie = (name: string) => { + let cookies = document.cookie.split('; ').reduce((res : any, el) => { + let cookie: Array = el.split('='); + res[cookie[0]] = cookie[1]; return res; }, {}); @@ -40,6 +54,6 @@ export const getCookie = (name) => { return cookies[name]; } -export const getAccessToken = (accessToken) => { +export const getAccessToken = (accessToken: string) => { return accessToken.split('Bearer ')[1]; } diff --git a/src/utils/data.js b/src/utils/data.js deleted file mode 100644 index 6ff8c71..0000000 --- a/src/utils/data.js +++ /dev/null @@ -1,214 +0,0 @@ -const INGREDIENTS = [ - { - "_id":"60666c42cc7b410027a1a9b1", - "name":"Краторная булка N-200i", - "type":"bun", - "proteins":80, - "fat":24, - "carbohydrates":53, - "calories":420, - "price":1255, - "image":"https://code.s3.yandex.net/react/code/bun-02.png", - "image_mobile":"https://code.s3.yandex.net/react/code/bun-02-mobile.png", - "image_large":"https://code.s3.yandex.net/react/code/bun-02-large.png", - "__v":0 - }, - { - "_id":"60666c42cc7b410027a1a9b5", - "name":"Говяжий метеорит (отбивная)", - "type":"main", - "proteins":800, - "fat":800, - "carbohydrates":300, - "calories":2674, - "price":3000, - "image":"https://code.s3.yandex.net/react/code/meat-04.png", - "image_mobile":"https://code.s3.yandex.net/react/code/meat-04-mobile.png", - "image_large":"https://code.s3.yandex.net/react/code/meat-04-large.png", - "__v":0 - }, - { - "_id":"60666c42cc7b410027a1a9b6", - "name":"Биокотлета из марсианской Магнолии", - "type":"main", - "proteins":420, - "fat":142, - "carbohydrates":242, - "calories":4242, - "price":424, - "image":"https://code.s3.yandex.net/react/code/meat-01.png", - "image_mobile":"https://code.s3.yandex.net/react/code/meat-01-mobile.png", - "image_large":"https://code.s3.yandex.net/react/code/meat-01-large.png", - "__v":0 - }, - { - "_id":"60666c42cc7b410027a1a9b7", - "name":"Соус Spicy-X", - "type":"sauce", - "proteins":30, - "fat":20, - "carbohydrates":40, - "calories":30, - "price":90, - "image":"https://code.s3.yandex.net/react/code/sauce-02.png", - "image_mobile":"https://code.s3.yandex.net/react/code/sauce-02-mobile.png", - "image_large":"https://code.s3.yandex.net/react/code/sauce-02-large.png", - "__v":0 - }, - { - "_id":"60666c42cc7b410027a1a9b4", - "name":"Мясо бессмертных моллюсков Protostomia", - "type":"main", - "proteins":433, - "fat":244, - "carbohydrates":33, - "calories":420, - "price":1337, - "image":"https://code.s3.yandex.net/react/code/meat-02.png", - "image_mobile":"https://code.s3.yandex.net/react/code/meat-02-mobile.png", - "image_large":"https://code.s3.yandex.net/react/code/meat-02-large.png", - "__v":0 - }, - { - "_id":"60666c42cc7b410027a1a9b9", - "name":"Соус традиционный галактический", - "type":"sauce", - "proteins":42, - "fat":24, - "carbohydrates":42, - "calories":99, - "price":15, - "image":"https://code.s3.yandex.net/react/code/sauce-03.png", - "image_mobile":"https://code.s3.yandex.net/react/code/sauce-03-mobile.png", - "image_large":"https://code.s3.yandex.net/react/code/sauce-03-large.png", - "__v":0 - }, - { - "_id":"60666c42cc7b410027a1a9b8", - "name":"Соус фирменный Space Sauce", - "type":"sauce", - "proteins":50, - "fat":22, - "carbohydrates":11, - "calories":14, - "price":80, - "image":"https://code.s3.yandex.net/react/code/sauce-04.png", - "image_mobile":"https://code.s3.yandex.net/react/code/sauce-04-mobile.png", - "image_large":"https://code.s3.yandex.net/react/code/sauce-04-large.png", - "__v":0 - }, - { - "_id":"60666c42cc7b410027a1a9bc", - "name":"Плоды Фалленианского дерева", - "type":"main", - "proteins":20, - "fat":5, - "carbohydrates":55, - "calories":77, - "price":874, - "image":"https://code.s3.yandex.net/react/code/sp_1.png", - "image_mobile":"https://code.s3.yandex.net/react/code/sp_1-mobile.png", - "image_large":"https://code.s3.yandex.net/react/code/sp_1-large.png", - "__v":0 - }, - { - "_id":"60666c42cc7b410027a1a9bb", - "name":"Хрустящие минеральные кольца", - "type":"main", - "proteins":808, - "fat":689, - "carbohydrates":609, - "calories":986, - "price":300, - "image":"https://code.s3.yandex.net/react/code/mineral_rings.png", - "image_mobile":"https://code.s3.yandex.net/react/code/mineral_rings-mobile.png", - "image_large":"https://code.s3.yandex.net/react/code/mineral_rings-large.png", - "__v":0 - }, - { - "_id":"60666c42cc7b410027a1a9ba", - "name":"Соус с шипами Антарианского плоскоходца", - "type":"sauce", - "proteins":101, - "fat":99, - "carbohydrates":100, - "calories":100, - "price":88, - "image":"https://code.s3.yandex.net/react/code/sauce-01.png", - "image_mobile":"https://code.s3.yandex.net/react/code/sauce-01-mobile.png", - "image_large":"https://code.s3.yandex.net/react/code/sauce-01-large.png", - "__v":0 - }, - { - "_id":"60666c42cc7b410027a1a9bd", - "name":"Кристаллы марсианских альфа-сахаридов", - "type":"main", - "proteins":234, - "fat":432, - "carbohydrates":111, - "calories":189, - "price":762, - "image":"https://code.s3.yandex.net/react/code/core.png", - "image_mobile":"https://code.s3.yandex.net/react/code/core-mobile.png", - "image_large":"https://code.s3.yandex.net/react/code/core-large.png", - "__v":0 - }, - { - "_id":"60666c42cc7b410027a1a9be", - "name":"Мини-салат Экзо-Плантаго", - "type":"main", - "proteins":1, - "fat":2, - "carbohydrates":3, - "calories":6, - "price":4400, - "image":"https://code.s3.yandex.net/react/code/salad.png", - "image_mobile":"https://code.s3.yandex.net/react/code/salad-mobile.png", - "image_large":"https://code.s3.yandex.net/react/code/salad-large.png", - "__v":0 - }, - { - "_id":"60666c42cc7b410027a1a9b3", - "name":"Филе Люминесцентного тетраодонтимформа", - "type":"main", - "proteins":44, - "fat":26, - "carbohydrates":85, - "calories":643, - "price":988, - "image":"https://code.s3.yandex.net/react/code/meat-03.png", - "image_mobile":"https://code.s3.yandex.net/react/code/meat-03-mobile.png", - "image_large":"https://code.s3.yandex.net/react/code/meat-03-large.png", - "__v":0 - }, - { - "_id":"60666c42cc7b410027a1a9bf", - "name":"Сыр с астероидной плесенью", - "type":"main", - "proteins":84, - "fat":48, - "carbohydrates":420, - "calories":3377, - "price":4142, - "image":"https://code.s3.yandex.net/react/code/cheese.png", - "image_mobile":"https://code.s3.yandex.net/react/code/cheese-mobile.png", - "image_large":"https://code.s3.yandex.net/react/code/cheese-large.png", - "__v":0 - }, - { - "_id":"60666c42cc7b410027a1a9b2", - "name":"Флюоресцентная булка R2-D3", - "type":"bun", - "proteins":44, - "fat":26, - "carbohydrates":85, - "calories":643, - "price":988, - "image":"https://code.s3.yandex.net/react/code/bun-01.png", - "image_mobile":"https://code.s3.yandex.net/react/code/bun-01-mobile.png", - "image_large":"https://code.s3.yandex.net/react/code/bun-01-large.png", - "__v":0 - } -]; - -export { INGREDIENTS }; diff --git a/src/utils/date.js b/src/utils/date.js deleted file mode 100644 index 427a626..0000000 --- a/src/utils/date.js +++ /dev/null @@ -1,23 +0,0 @@ -export function getFormattedDate(dateStr) { - const dateNow = new Date(); - const date = new Date(dateStr); - const dateTimezone = 'i-GMT' + date.getTimezoneOffset()/60; - const hours = date.getHours(); - const formattedHours = hours < 10 ? '0' + hours : hours; - const minutes = date.getMinutes(); - const formattedMinutes = minutes < 10 ? '0' + minutes : minutes; - const dateOffsetHours = (dateNow - date)/3600000; - - let dateFormat = ''; - - if (dateNow.toDateString() === date.toDateString()) { // Если даты совпадают - dateFormat = 'Сегодня'; - } else if (dateOffsetHours < 24) { // Если прошло не более суток - dateFormat = 'Вчера'; - } else { - dateFormat = Math.floor(dateOffsetHours/24); - dateFormat < 5 ? dateFormat += ' дня' : dateFormat += ' дней'; - } - - return dateFormat + ' ' + formattedHours + ':' + formattedMinutes + ' ' + dateTimezone; -} \ No newline at end of file diff --git a/src/utils/date.tsx b/src/utils/date.tsx new file mode 100644 index 0000000..f7ab0e8 --- /dev/null +++ b/src/utils/date.tsx @@ -0,0 +1,24 @@ +export function getFormattedDate(dateStr: string | Date) { + const dateNow: Date = new Date(); + const date: Date = new Date(dateStr); + const dateTimezone: string = 'i-GMT' + date.getTimezoneOffset()/60; + const hours: number = date.getHours(); + const formattedHours: string = hours < 10 ? '0' + hours : hours.toString(); + const minutes: number = date.getMinutes(); + const formattedMinutes: string = minutes < 10 ? '0' + minutes : minutes.toString(); + const dateOffsetHours: number = (dateNow.getTime() - date.getTime())/3600000; + + let dateFormat: string = ''; + + if (dateNow.toDateString() === date.toDateString()) { // Если даты совпадают + dateFormat = 'Сегодня'; + } else if (dateOffsetHours < 24) { // Если прошло не более суток + dateFormat = 'Вчера'; + } else { + const dateNumber: number = Math.floor(dateOffsetHours/24); + dateFormat += dateNumber; + dateNumber < 5 ? dateFormat += ' дня' : dateFormat += ' дней'; + } + + return dateFormat + ' ' + formattedHours + ':' + formattedMinutes + ' ' + dateTimezone; +} \ No newline at end of file diff --git a/src/utils/order.js b/src/utils/order.js deleted file mode 100644 index 6ec4aa9..0000000 --- a/src/utils/order.js +++ /dev/null @@ -1,132 +0,0 @@ -const ORDER = { - 'bun': { - "_id": "60d3b41abdacab0026a733c6", - "name": "Краторная булка N-200i", - "type": "bun", - "proteins": 80, - "fat": 24, - "carbohydrates": 53, - "calories": 420, - "price": 1255, - "image": "https://code.s3.yandex.net/react/code/bun-02.png", - "image_mobile": "https://code.s3.yandex.net/react/code/bun-02-mobile.png", - "image_large": "https://code.s3.yandex.net/react/code/bun-02-large.png", - "__v": 0 - }, - 'others': [ - { - "_id": "60d3b41abdacab0026a733c9", - "name": "Мясо бессмертных моллюсков Protostomia", - "type": "main", - "proteins": 433, - "fat": 244, - "carbohydrates": 33, - "calories": 420, - "price": 1337, - "image": "https://code.s3.yandex.net/react/code/meat-02.png", - "image_mobile": "https://code.s3.yandex.net/react/code/meat-02-mobile.png", - "image_large": "https://code.s3.yandex.net/react/code/meat-02-large.png", - "__v": 0 - }, - { - "_id": "60d3b41abdacab0026a733cb", - "name": "Биокотлета из марсианской Магнолии", - "type": "main", - "proteins": 420, - "fat": 142, - "carbohydrates": 242, - "calories": 4242, - "price": 424, - "image": "https://code.s3.yandex.net/react/code/meat-01.png", - "image_mobile": "https://code.s3.yandex.net/react/code/meat-01-mobile.png", - "image_large": "https://code.s3.yandex.net/react/code/meat-01-large.png", - "__v": 0 - }, - { - "_id": "60d3b41abdacab0026a733cc", - "name": "Соус Spicy-X", - "type": "sauce", - "proteins": 30, - "fat": 20, - "carbohydrates": 40, - "calories": 30, - "price": 90, - "image": "https://code.s3.yandex.net/react/code/sauce-02.png", - "image_mobile": "https://code.s3.yandex.net/react/code/sauce-02-mobile.png", - "image_large": "https://code.s3.yandex.net/react/code/sauce-02-large.png", - "__v": 0 - }, - { - "_id": "60d3b41abdacab0026a733d1", - "name": "Плоды Фалленианского дерева", - "type": "main", - "proteins": 20, - "fat": 5, - "carbohydrates": 55, - "calories": 77, - "price": 874, - "image": "https://code.s3.yandex.net/react/code/sp_1.png", - "image_mobile": "https://code.s3.yandex.net/react/code/sp_1-mobile.png", - "image_large": "https://code.s3.yandex.net/react/code/sp_1-large.png", - "__v": 0 - }, - { - "_id": "60d3b41abdacab0026a733d0", - "name": "Хрустящие минеральные кольца", - "type": "main", - "proteins": 808, - "fat": 689, - "carbohydrates": 609, - "calories": 986, - "price": 300, - "image": "https://code.s3.yandex.net/react/code/mineral_rings.png", - "image_mobile": "https://code.s3.yandex.net/react/code/mineral_rings-mobile.png", - "image_large": "https://code.s3.yandex.net/react/code/mineral_rings-large.png", - "__v": 0 - }, - { - "_id": "60d3b41abdacab0026a733d3", - "name": "Мини-салат Экзо-Плантаго", - "type": "main", - "proteins": 1, - "fat": 2, - "carbohydrates": 3, - "calories": 6, - "price": 4400, - "image": "https://code.s3.yandex.net/react/code/salad.png", - "image_mobile": "https://code.s3.yandex.net/react/code/salad-mobile.png", - "image_large": "https://code.s3.yandex.net/react/code/salad-large.png", - "__v": 0 - }, - { - "_id": "60d3b41abdacab0026a733d3", - "name": "Мини-салат Экзо-Плантаго", - "type": "main", - "proteins": 1, - "fat": 2, - "carbohydrates": 3, - "calories": 6, - "price": 4400, - "image": "https://code.s3.yandex.net/react/code/salad.png", - "image_mobile": "https://code.s3.yandex.net/react/code/salad-mobile.png", - "image_large": "https://code.s3.yandex.net/react/code/salad-large.png", - "__v": 0 - }, - { - "_id": "60d3b41abdacab0026a733d4", - "name": "Сыр с астероидной плесенью", - "type": "main", - "proteins": 84, - "fat": 48, - "carbohydrates": 420, - "calories": 3377, - "price": 4142, - "image": "https://code.s3.yandex.net/react/code/cheese.png", - "image_mobile": "https://code.s3.yandex.net/react/code/cheese-mobile.png", - "image_large": "https://code.s3.yandex.net/react/code/cheese-large.png", - "__v": 0 - } - ] -} - -export { ORDER }; diff --git a/src/utils/propTypes.js b/src/utils/propTypes.js deleted file mode 100644 index 0954f1f..0000000 --- a/src/utils/propTypes.js +++ /dev/null @@ -1,18 +0,0 @@ -import PropTypes from 'prop-types'; - -const ingredientsPropTypes = PropTypes.shape({ - _id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - type: PropTypes.string.isRequired, - proteins:PropTypes.number, - fat: PropTypes.number, - carbohydrates: PropTypes.number, - calories: PropTypes.number, - price: PropTypes.number.isRequired, - image: PropTypes.string.isRequired, - image_mobile: PropTypes.string.isRequired, - image_large: PropTypes.string.isRequired, - __v: PropTypes.number -}); - -export default ingredientsPropTypes; diff --git a/tsconfig.json b/tsconfig.json index a273b0c..34bbca6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es5", + "target": "ESNext", "lib": [ "dom", "dom.iterable",