diff --git a/package-lock.json b/package-lock.json index 0ad8646..bda9a2c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,8 @@ "react-dom": "18.2.0", "react-helmet-async": "1.3.0", "react-redux": "8.1.3", - "react-router-dom": "6.16.0" + "react-router-dom": "6.16.0", + "react-toastify": "^10.0.6" }, "devDependencies": { "@jedmao/redux-mock-store": "3.0.5", @@ -2305,6 +2306,14 @@ "node": ">=0.8.0" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2347,9 +2356,9 @@ "dev": true }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "dependencies": { "path-key": "^3.1.0", @@ -5227,6 +5236,18 @@ "react-dom": ">=16.8" } }, + "node_modules/react-toastify": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.6.tgz", + "integrity": "sha512-yYjp+omCDf9lhZcrZHKbSq7YMuK0zcYkDFTzfRFgTXkTFHZ1ToxwAonzA4JI5CxA91JpjFLmwEsZEgfYfOqI1A==", + "dependencies": { + "clsx": "^2.1.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, "node_modules/read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", @@ -5476,9 +5497,9 @@ } }, "node_modules/rollup": { - "version": "3.29.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", - "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", + "version": "3.29.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz", + "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==", "dev": true, "bin": { "rollup": "dist/bin/rollup" diff --git a/package.json b/package.json index 5a67c5f..c9d4c81 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "react-dom": "18.2.0", "react-helmet-async": "1.3.0", "react-redux": "8.1.3", - "react-router-dom": "6.16.0" + "react-router-dom": "6.16.0", + "react-toastify": "^10.0.6" }, "devDependencies": { "@jedmao/redux-mock-store": "3.0.5", diff --git a/src/browser-history.ts b/src/browser-history.ts new file mode 100644 index 0000000..55c728c --- /dev/null +++ b/src/browser-history.ts @@ -0,0 +1,3 @@ +import {createBrowserHistory} from 'history'; + +export const browserHistory = createBrowserHistory(); diff --git a/src/components/app/app.tsx b/src/components/app/app.tsx index 3ea609d..6e968ef 100644 --- a/src/components/app/app.tsx +++ b/src/components/app/app.tsx @@ -1,57 +1,61 @@ import {MainPage} from '../../pages/main-page/main-page'; -import {BrowserRouter, Route, Routes} from 'react-router-dom'; +import {Route, Routes} from 'react-router-dom'; import {HelmetProvider} from 'react-helmet-async'; import {AppRoute} from '../../const.ts'; import {LoginPage} from '../../pages/login-page/login-page.tsx'; import {PrivateRoute} from '../private-route/private-route.tsx'; import {OfferPage} from '../../pages/offer-page/offer-page.tsx'; import {NotFoundPage} from '../../pages/404-not-found-page/not-found-page.tsx'; -import {AuthorizationStatus} from '../../const.ts'; import {FavoritesPage} from '../../pages/favorites-page/favorites-page.tsx'; import {useAppSelector} from '../../hooks'; import {Spinner} from '../spinner/spinner.tsx'; +import {HistoryRouter} from '../history-route/history-route.tsx'; +import {browserHistory} from '../../browser-history.ts'; +import {PrivateLoginRoute} from '../private-login-route/private-login-route.tsx'; -export function App() : JSX.Element { - const isOffersDataLoading = useAppSelector((state) => state.isOffersDataLoading); +export function App(): JSX.Element { + const isOffersDataLoading = useAppSelector((state) => state.isDataLoading); if (isOffersDataLoading) { return ( - + ); } return ( - + } + element={} /> } + element={ + + + + } /> - + + } /> } + element={} /> } + element={} /> - + ); } diff --git a/src/components/custom-toast/custom-toast.tsx b/src/components/custom-toast/custom-toast.tsx new file mode 100644 index 0000000..af0b0b5 --- /dev/null +++ b/src/components/custom-toast/custom-toast.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { ToastContainer, toast } from 'react-toastify'; +import 'react-toastify/dist/ReactToastify.css'; + +const CustomToast: React.FC<{ message: string }> = ({ message }) => ( +
+ {message} +
+); + +export const showCustomToast = (message: string) => { + toast(); +}; + +export function CustomToastContainer() { + return ( + + ); +} diff --git a/src/components/favorite-card/favorite-card.tsx b/src/components/favorite-card/favorite-card.tsx index 5a8d42e..c812463 100644 --- a/src/components/favorite-card/favorite-card.tsx +++ b/src/components/favorite-card/favorite-card.tsx @@ -8,14 +8,14 @@ type FavoriteCardProps = { export function FavoriteCard({offer}: FavoriteCardProps): JSX.Element { return ( -
+
{offer.isPremium &&
Premium
} -
+
diff --git a/src/components/favorites-list/favorites-list.tsx b/src/components/favorites-list/favorites-list.tsx index 1251c2d..dc92f87 100644 --- a/src/components/favorites-list/favorites-list.tsx +++ b/src/components/favorites-list/favorites-list.tsx @@ -7,15 +7,14 @@ type FavoritesListProps = { export function FavoritesList({offers} : FavoritesListProps) { const groupedOffers = offers.reduce((acc, offer) => { - if (offer.isFavorite) { - const cityName = offer.city.name; + const cityName = offer.city.name; - if (!acc[cityName]) { - acc[cityName] = []; - } - - acc[cityName].push(offer); + if (!acc[cityName]) { + acc[cityName] = []; } + + acc[cityName].push(offer); + return acc; }, {} as Record); diff --git a/src/components/header/header.tsx b/src/components/header/header.tsx new file mode 100644 index 0000000..38d6ac5 --- /dev/null +++ b/src/components/header/header.tsx @@ -0,0 +1,62 @@ +import {useAppDispatch, useAppSelector} from '../../hooks'; +import {Link, useLocation} from 'react-router-dom'; +import {AppRoute} from '../../const.ts'; +import {logoutAction} from '../../store/api-actions.ts'; + +export function Header() { + const dispatch = useAppDispatch(); + const userData = useAppSelector((state) => state.userData); + const favoriteOffers = useAppSelector((state) => state.favoriteOffers); + const location = useLocation(); + + const isLoginPage = location.pathname === AppRoute.Login as string; + + return ( +
+
+
+
+ + 6 cities logo + +
+ {!isLoginPage && ( + + )} +
+
+
+ ); +} diff --git a/src/components/history-route/history-route.tsx b/src/components/history-route/history-route.tsx new file mode 100644 index 0000000..58e52df --- /dev/null +++ b/src/components/history-route/history-route.tsx @@ -0,0 +1,33 @@ +import React, {useState, useLayoutEffect} from 'react'; +import {Router} from 'react-router-dom'; +import type {BrowserHistory} from 'history'; + +export interface HistoryRouterProps { + history: BrowserHistory; + basename?: string; + children?: React.ReactNode; +} + +export function HistoryRouter({ + basename, + children, + history, +}: HistoryRouterProps) { + const [state, setState] = useState({ + action: history.action, + location: history.location, + }); + + useLayoutEffect(() => history.listen(setState), [history]); + + return ( + + {children} + + ); +} diff --git a/src/components/login-form/login-form.tsx b/src/components/login-form/login-form.tsx new file mode 100644 index 0000000..cb68719 --- /dev/null +++ b/src/components/login-form/login-form.tsx @@ -0,0 +1,64 @@ +import {FormEvent, useRef} from 'react'; +import {useAppDispatch} from '../../hooks'; +import {loginAction} from '../../store/api-actions.ts'; +import {showCustomToast} from '../custom-toast/custom-toast.tsx'; + +const validatePassword = (password: string): boolean => { + const hasLetter = /[a-zA-Z]/.test(password); + const hasNumber = /\d/.test(password); + const hasNoSpaces = !/\s/.test(password); + + return hasLetter && hasNumber && hasNoSpaces; +}; + +export function LoginForm() { + const loginFormRef = useRef(null); + + const dispatch = useAppDispatch(); + + const handleSubmit = (evt: FormEvent) => { + evt.preventDefault(); + + if (!loginFormRef.current) { + return; + } + + const formData = new FormData(loginFormRef.current); + const email = formData.get('email') as string; + const password = formData.get('password') as string; + + if (!validatePassword(password)) { + showCustomToast('Password must contain at least one letter, one number, and no spaces.'); + return; + } + + if (email && password) { + dispatch(loginAction({ + email, + password + })); + } + }; + + return ( +
+

Sign in

+
+
+ + +
+
+ + +
+ +
+
+ ); +} diff --git a/src/components/private-login-route/private-login-route.tsx b/src/components/private-login-route/private-login-route.tsx new file mode 100644 index 0000000..2b6b587 --- /dev/null +++ b/src/components/private-login-route/private-login-route.tsx @@ -0,0 +1,17 @@ +import {Navigate} from 'react-router-dom'; +import {AppRoute, AuthorizationStatus} from '../../const'; +import {useAppSelector} from '../../hooks'; + +type PrivateLoginRouteProps = { + children: JSX.Element; +} + +export function PrivateLoginRoute({children}: PrivateLoginRouteProps): JSX.Element { + const authorizationStatus = useAppSelector((state) => state.authorizationStatus); + + return ( + authorizationStatus !== AuthorizationStatus.Auth + ? children + : + ); +} diff --git a/src/components/private-route/private-route.tsx b/src/components/private-route/private-route.tsx index 9a56759..145ed40 100644 --- a/src/components/private-route/private-route.tsx +++ b/src/components/private-route/private-route.tsx @@ -1,12 +1,14 @@ import {Navigate} from 'react-router-dom'; import {AppRoute, AuthorizationStatus} from '../../const'; +import {useAppSelector} from '../../hooks'; type PrivateRouteProps = { - authorizationStatus: AuthorizationStatus; children: JSX.Element; } -export function PrivateRoute({authorizationStatus, children}: PrivateRouteProps): JSX.Element { +export function PrivateRoute({children}: PrivateRouteProps): JSX.Element { + const authorizationStatus = useAppSelector((state) => state.authorizationStatus); + return ( authorizationStatus === AuthorizationStatus.Auth ? children diff --git a/src/const.ts b/src/const.ts index d51bb60..a4fd354 100644 --- a/src/const.ts +++ b/src/const.ts @@ -5,11 +5,14 @@ export enum AppRoute { Login = '/login', Favorites = '/favorites', Offer = '/offer', - OfferWithId = '/offer/:id' + OfferWithId = '/offer/:id', } export enum APIRoute { - Offers = '/offers' + Offers = '/offers', + Login = '/login', + Logout = '/logout', + Favorite = '/favorite', } export enum AuthorizationStatus { @@ -24,5 +27,5 @@ export const SORTING_OPTIONS : SortingOption[] = [ 'Popular', 'Price: low to high', 'Price: high to low', - 'Top rated first' + 'Top rated first', ]; diff --git a/src/index.tsx b/src/index.tsx index 76b3513..2aff7f2 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -3,8 +3,10 @@ import ReactDOM from 'react-dom/client'; import {App} from './components/app/app'; import {Provider} from 'react-redux'; import {store} from './store'; -import {fetchOffersAction} from './store/api-actions.ts'; +import {checkAuthAction, fetchOffersAction} from './store/api-actions.ts'; +import {CustomToastContainer} from './components/custom-toast/custom-toast.tsx'; +store.dispatch(checkAuthAction()); store.dispatch(fetchOffersAction()); const root = ReactDOM.createRoot( @@ -14,6 +16,7 @@ const root = ReactDOM.createRoot( root.render( + diff --git a/src/pages/favorites-page/favorites-page.tsx b/src/pages/favorites-page/favorites-page.tsx index a3f598c..e6f07d0 100644 --- a/src/pages/favorites-page/favorites-page.tsx +++ b/src/pages/favorites-page/favorites-page.tsx @@ -1,49 +1,23 @@ import {Helmet} from 'react-helmet-async'; import {FavoritesList} from '../../components/favorites-list/favorites-list.tsx'; import {useAppSelector} from '../../hooks'; +import {Header} from '../../components/header/header.tsx'; export function FavoritesPage() : JSX.Element { - const offers = useAppSelector((state) => state.offers); + const favoriteOffers = useAppSelector((state) => state.favoriteOffers); return (
Favorites - 6 cities -
- -
+

Saved listing

- +
diff --git a/src/pages/login-page/login-page.tsx b/src/pages/login-page/login-page.tsx index 22c7f25..193cfcd 100644 --- a/src/pages/login-page/login-page.tsx +++ b/src/pages/login-page/login-page.tsx @@ -1,4 +1,6 @@ import {Helmet} from 'react-helmet-async'; +import {LoginForm} from '../../components/login-form/login-form.tsx'; +import {Header} from '../../components/header/header.tsx'; export function LoginPage() : JSX.Element { return ( @@ -6,34 +8,11 @@ export function LoginPage() : JSX.Element { Log In - 6 cities -
-
-
-
- - 6 cities logo - -
-
-
-
+
-
-

Sign in

-
-
- - -
-
- - -
- -
-
+
diff --git a/src/pages/main-page/main-page.tsx b/src/pages/main-page/main-page.tsx index a930735..80ab7ba 100644 --- a/src/pages/main-page/main-page.tsx +++ b/src/pages/main-page/main-page.tsx @@ -9,6 +9,7 @@ import {setCity} from '../../store/action.ts'; import {useMemo, useState} from 'react'; import {SortingOption} from '../../types/sorting-option.ts'; import {SortingOptions} from '../../components/sorting-options/sorting-options.tsx'; +import {Header} from '../../components/header/header.tsx'; function getPlacesText(count: number): string { if (count === 1) { @@ -64,33 +65,7 @@ export function MainPage(): JSX.Element { 6 cities -
- -
+

Cities

diff --git a/src/services/api.ts b/src/services/api.ts index e36fec6..b42a85c 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -1,9 +1,54 @@ -import axios, {AxiosInstance} from 'axios'; +import axios, {AxiosError, AxiosInstance, AxiosResponse, InternalAxiosRequestConfig} from 'axios'; +import {getToken} from './token.ts'; +import {StatusCodes} from 'http-status-codes'; +import {showCustomToast} from '../components/custom-toast/custom-toast.tsx'; + +type DetailMessageType = { + type: string; + message: string; +} + +const StatusCodeMapping: Record = { + [StatusCodes.BAD_REQUEST]: true, + [StatusCodes.UNAUTHORIZED]: true, + [StatusCodes.NOT_FOUND]: true +}; + +const shouldDisplayError = (response: AxiosResponse) => StatusCodeMapping[response.status]; const BACKEND_URL = 'https://14.design.htmlacademy.pro/six-cities'; const REQUEST_TIMEOUT = 5000; -export const createAPI = (): AxiosInstance => axios.create({ - baseURL: BACKEND_URL, - timeout: REQUEST_TIMEOUT, -}); +export const createAPI = (): AxiosInstance => { + const api = axios.create({ + baseURL: BACKEND_URL, + timeout: REQUEST_TIMEOUT, + }); + + api.interceptors.request.use( + (config: InternalAxiosRequestConfig) => { + const token = getToken(); + + if (token && config.headers) { + config.headers['x-token'] = token; + } + + return config; + }, + ); + + api.interceptors.response.use( + (response) => response, + (error: AxiosError) => { + if (error.response && shouldDisplayError(error.response)) { + const detailMessage = (error.response.data); + + showCustomToast(detailMessage.message); + } + + throw error; + } + ); + + return api; +}; diff --git a/src/services/token.ts b/src/services/token.ts new file mode 100644 index 0000000..df439d4 --- /dev/null +++ b/src/services/token.ts @@ -0,0 +1,16 @@ +const AUTH_TOKEN_KEY_NAME = 'six-cities-token'; + +export type Token = string; + +export const getToken = (): Token => { + const token = localStorage.getItem(AUTH_TOKEN_KEY_NAME); + return token ?? ''; +}; + +export const saveToken = (token: Token): void => { + localStorage.setItem(AUTH_TOKEN_KEY_NAME, token); +}; + +export const dropToken = (): void => { + localStorage.removeItem(AUTH_TOKEN_KEY_NAME); +}; diff --git a/src/store/action.ts b/src/store/action.ts index 6176f11..19de4fe 100644 --- a/src/store/action.ts +++ b/src/store/action.ts @@ -1,8 +1,18 @@ import { createAction } from '@reduxjs/toolkit'; import {Offer} from '../types/offer.ts'; +import {AppRoute, AuthorizationStatus} from '../const.ts'; +import {UserData} from '../types/user.ts'; export const setCity = createAction('city/setCity'); export const setOffers = createAction('offers/setOffers'); -export const setOffersDataLoadingStatus = createAction('data/setOffersDataLoadingStatus'); +export const setDataLoadingStatus = createAction('data/setDataLoadingStatus'); + +export const requireAuthorization = createAction('user/requireAuthorization'); + +export const setUserData = createAction('user/setUserData'); + +export const setFavoriteOffers = createAction('user/setFavoriteOffers'); + +export const redirectToRoute = createAction('auth/redirectToRoute'); diff --git a/src/store/api-actions.ts b/src/store/api-actions.ts index ccc0ad7..31a3e55 100644 --- a/src/store/api-actions.ts +++ b/src/store/api-actions.ts @@ -2,19 +2,90 @@ import {AxiosInstance} from 'axios'; import {AppDispatch, State} from '../types/state.ts'; import {createAsyncThunk} from '@reduxjs/toolkit'; import {Offer} from '../types/offer.ts'; -import {APIRoute} from '../const.ts'; -import {setOffers, setOffersDataLoadingStatus} from './action.ts'; +import {APIRoute, AppRoute, AuthorizationStatus} from '../const.ts'; +import { + redirectToRoute, + requireAuthorization, + setOffers, + setDataLoadingStatus, + setUserData, + setFavoriteOffers +} from './action.ts'; +import {dropToken, saveToken} from '../services/token.ts'; +import {AuthData, UserData} from '../types/user.ts'; export const fetchOffersAction = createAsyncThunk( - 'data/fetchQuestions', + 'data/fetchOffers', async (_arg, {dispatch, extra: api}) => { - dispatch(setOffersDataLoadingStatus(true)); + dispatch(setDataLoadingStatus(true)); const {data} = await api.get(APIRoute.Offers); - dispatch(setOffersDataLoadingStatus(false)); + dispatch(setDataLoadingStatus(false)); dispatch(setOffers(data)); }, ); + +export const fetchFavoriteOffersAction = createAsyncThunk( + 'data/fetchFavoriteOffers', + async (_arg, {dispatch, extra: api}) => { + const {data} = await api.get(APIRoute.Favorite); + dispatch(setFavoriteOffers(data)); + }, +); + +export const checkAuthAction = createAsyncThunk( + 'user/checkAuth', + async (_arg, {dispatch, extra: api}) => { + try { + const response = await api.get(APIRoute.Login); + dispatch(requireAuthorization(AuthorizationStatus.Auth)); + dispatch(setUserData(response.data)); + dispatch(fetchFavoriteOffersAction()); + } catch (error) { + dispatch(requireAuthorization(AuthorizationStatus.NoAuth)); + } + }, +); + +export const loginAction = createAsyncThunk( + 'user/login', + async ({email, password}, {dispatch, extra: api}) => { + const response = await api.post(APIRoute.Login, {email, password}); + saveToken(response.data.token); + dispatch(requireAuthorization(AuthorizationStatus.Auth)); + dispatch(setUserData(response.data)); + dispatch(fetchOffersAction()); + dispatch(fetchFavoriteOffersAction()); + dispatch(redirectToRoute(AppRoute.Main)); + }, +); + +export const logoutAction = createAsyncThunk( + 'user/logout', + async (_arg, {dispatch, extra: api}) => { + await api.delete(APIRoute.Logout); + dropToken(); + dispatch(requireAuthorization(AuthorizationStatus.NoAuth)); + dispatch(setUserData(null)); + dispatch(setFavoriteOffers([])); + }, +); diff --git a/src/store/index.ts b/src/store/index.ts index 7ef91d2..e75099b 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1,6 +1,7 @@ import { configureStore } from '@reduxjs/toolkit'; import { reducer } from './reducer'; import {createAPI} from '../services/api'; +import {redirect} from './middlewares/redirect.ts'; export const api = createAPI(); @@ -11,5 +12,5 @@ export const store = configureStore({ thunk: { extraArgument: api, }, - }), + }).concat(redirect), }); diff --git a/src/store/middlewares/redirect.ts b/src/store/middlewares/redirect.ts new file mode 100644 index 0000000..902f42a --- /dev/null +++ b/src/store/middlewares/redirect.ts @@ -0,0 +1,17 @@ +import {PayloadAction} from '@reduxjs/toolkit'; +import {browserHistory} from '../../browser-history'; +import {Middleware} from 'redux'; +import {reducer} from '../reducer'; + +type Reducer = ReturnType; + +export const redirect: Middleware = + () => + (next) => + (action: PayloadAction) => { + if (action.type === 'auth/redirectToRoute') { + browserHistory.push(action.payload); + } + + return next(action); + }; diff --git a/src/store/reducer.ts b/src/store/reducer.ts index fd51a3d..25c8347 100644 --- a/src/store/reducer.ts +++ b/src/store/reducer.ts @@ -1,17 +1,25 @@ import { createReducer } from '@reduxjs/toolkit'; -import {setCity, setOffers, setOffersDataLoadingStatus} from './action'; +import {requireAuthorization, setCity, setOffers, setDataLoadingStatus, setUserData, setFavoriteOffers} from './action'; import {Offer} from '../types/offer.ts'; +import {AuthorizationStatus} from '../const.ts'; +import {UserData} from '../types/user.ts'; type InitialState = { activeCity: string; offers: Offer[]; - isOffersDataLoading: boolean; + favoriteOffers: Offer[]; + isDataLoading: boolean; + authorizationStatus: AuthorizationStatus; + userData: UserData | null; } const initialState: InitialState = { activeCity: 'Paris', offers: [], - isOffersDataLoading: false + favoriteOffers: [], + isDataLoading: false, + authorizationStatus: AuthorizationStatus.Unknown, + userData: null, }; export const reducer = createReducer(initialState, (builder) => { @@ -22,7 +30,16 @@ export const reducer = createReducer(initialState, (builder) => { .addCase(setOffers, (state, action) => { state.offers = action.payload; }) - .addCase(setOffersDataLoadingStatus, (state, action) => { - state.isOffersDataLoading = action.payload; + .addCase(setFavoriteOffers, (state, action) => { + state.favoriteOffers = action.payload; + }) + .addCase(setDataLoadingStatus, (state, action) => { + state.isDataLoading = action.payload; + }) + .addCase(requireAuthorization, (state, action) => { + state.authorizationStatus = action.payload; + }) + .addCase(setUserData, (state, action) => { + state.userData = action.payload; }); }); diff --git a/src/types/user.ts b/src/types/user.ts index 9a1670e..2b5d4bf 100644 --- a/src/types/user.ts +++ b/src/types/user.ts @@ -3,3 +3,16 @@ export type User = { avatarUrl: string; isPro: boolean; } + +export type UserData = { + name: string; + avatarUrl: string; + isPro: boolean; + email: string; + token: string; +} + +export type AuthData = { + email: string; + password: string; +}