@@ -9,23 +47,44 @@ function LoginPage() : JSX.Element {
diff --git a/src/pages/offer-page/offer-page.tsx b/src/pages/offer-page/offer-page.tsx
index 1569e55..aee299f 100644
--- a/src/pages/offer-page/offer-page.tsx
+++ b/src/pages/offer-page/offer-page.tsx
@@ -1,5 +1,4 @@
import Header from '../../components/header/header.tsx';
-import {OffersDetailed} from '../../types/offers/offer-detailed.ts';
import {useParams} from 'react-router-dom';
import PremiumSign from '../../components/premium-sign/premium-sign.tsx';
import Rating from '../../components/rating/rating.tsx';
@@ -14,21 +13,52 @@ import OfferFeatures from '../../components/offer/offer-features.tsx';
import OfferInside from '../../components/offer/offer-inside.tsx';
import {Points} from '../../types/point.ts';
import {OffersShort} from '../../types/offers/offer-short.ts';
+import {OfferDetailed} from '../../types/offers/offer-detailed.ts';
+import {CityName} from '../../const.ts';
-type OfferPageProps = {
- offersDetailed: OffersDetailed;
-}
-
-function OfferPage({offersDetailed,}: OfferPageProps): JSX.Element | null {
+function OfferPage(): JSX.Element | null {
const {id} = useParams();
- const nearbyOffers : OffersShort = [];
- const nearbyPoints : Points = nearbyOffers.map((offer) => ({id: offer.id, location: offer.location}));
+ const nearbyOffers: OffersShort = [];
+ const nearbyPoints: Points = nearbyOffers.map((offer) => ({id: offer.id, location: offer.location}));
if (!id) {
return null;
}
- const currentOffer = offersDetailed.get(id);
+ const currentOffer: OfferDetailed = {
+ id: 'string',
+ title: 'string',
+ type: 'string',
+ price: 0,
+ city: {
+ name: CityName.Amsterdam,
+ location: {
+ latitude: 0,
+ longitude: 0,
+ zoom: 0,
+ }
+ },
+ location: {
+ latitude: 0,
+ longitude: 0,
+ zoom: 0,
+ },
+ isFavorite: false,
+ isPremium: false,
+ rating: 0,
+ description: 'string',
+ bedrooms: 1,
+ goods: [''],
+ host: {
+ name: 'string',
+ avatarUrl: 'string',
+ isPro: false,
+ email: 'string',
+ token: 'string'
+ },
+ images: [''],
+ maxAdults: 1,
+ };
if (!currentOffer) {
return null;
diff --git a/src/services/api.ts b/src/services/api.ts
index 51ba0f7..915f51e 100644
--- a/src/services/api.ts
+++ b/src/services/api.ts
@@ -1,4 +1,7 @@
-import axios, {AxiosInstance} from 'axios';
+import axios, {AxiosError, AxiosInstance, InternalAxiosRequestConfig} from 'axios';
+import {getToken} from './token.ts';
+import {toast} from 'react-toastify';
+import {DetailMessageType, shouldDisplayError} from './error-handle.ts';
const BACKEND_URL = 'https://14.design.htmlacademy.pro/six-cities';
const REQUEST_TIMEOUT = 5000;
@@ -9,5 +12,40 @@ export const createAPI = (): AxiosInstance => {
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)) {
+ if (error.response.data.details.length) {
+ const messages = (error.response.data.details);
+ messages.map((property) => {
+ property.messages.map((message) => {
+
+ toast.warning(message);
+ });
+ });
+ } else {
+ const detailMessage = (error.response.data);
+ toast.warn(detailMessage.message);
+ }
+ }
+
+ throw error;
+ }
+ );
+
return api;
};
diff --git a/src/services/error-handle.ts b/src/services/error-handle.ts
new file mode 100644
index 0000000..bef6b2b
--- /dev/null
+++ b/src/services/error-handle.ts
@@ -0,0 +1,20 @@
+import {StatusCodes} from 'http-status-codes';
+import {AxiosResponse} from 'axios';
+
+export type DetailMessageType = {
+ errorType: string;
+ message: string;
+ details: [{
+ property: string;
+ value: string;
+ messages: [string];
+ }];
+}
+
+const StatusCodeMapping: Record = {
+ [StatusCodes.BAD_REQUEST]: true,
+ [StatusCodes.UNAUTHORIZED]: true,
+ [StatusCodes.NOT_FOUND]: true
+};
+
+export const shouldDisplayError = (response: AxiosResponse) => StatusCodeMapping[response.status];
diff --git a/src/services/token.ts b/src/services/token.ts
new file mode 100644
index 0000000..de45fe9
--- /dev/null
+++ b/src/services/token.ts
@@ -0,0 +1,16 @@
+const AUTH_TOKEN_KEY_NAME = '6-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/acions.ts b/src/store/acions.ts
index 212e029..0df83d0 100644
--- a/src/store/acions.ts
+++ b/src/store/acions.ts
@@ -3,6 +3,8 @@ import {City} from '../types/city.ts';
import {Nullable} from '../types/nullable.ts';
import {SortingOrder} from '../types/sorting-order.ts';
import {OffersShort} from '../types/offers/offer-short.ts';
+import {AuthStatus} from '../const.ts';
+import {UserData} from '../types/user-data.ts';
export const setActiveCity = createAction('setCity');
export const setHoverCardId = createAction>('setHooverCardId');
@@ -12,3 +14,7 @@ export const setSortingOrder = createAction('setSortingOrder');
export const setOffers = createAction('setOffers');
export const setOffersLoading = createAction('setOffersLoading');
+
+export const setAuthStatus = createAction('setAuthStatus');
+
+export const setUserData = createAction>('setUserData');
diff --git a/src/store/api-actions.ts b/src/store/api-actions.ts
index b4be976..b933a52 100644
--- a/src/store/api-actions.ts
+++ b/src/store/api-actions.ts
@@ -1,14 +1,17 @@
import {createAsyncThunk} from '@reduxjs/toolkit';
import {OffersShort} from '../types/offers/offer-short.ts';
import {AxiosInstance} from 'axios';
-import {APIRoute} from '../const.ts';
-import {AppDispatch} from './index.tsx';
-import {setOffersLoading} from './acions.ts';
+import {APIRoute, AuthStatus} from '../const.ts';
+import {AppDispatch, RootState} from './index.tsx';
+import {setAuthStatus, setOffersLoading, setUserData} from './acions.ts';
+import {dropToken, saveToken} from '../services/token.ts';
+import {UserData} from '../types/user-data.ts';
+import {AuthData} from '../types/auth-data.ts';
-export const fetchOffers = createAsyncThunk<
+export const fetchOffersAction = createAsyncThunk<
OffersShort,
- undefined,{
+ undefined, {
dispatch: AppDispatch;
extra: AxiosInstance;
}>(
@@ -20,3 +23,52 @@ export const fetchOffers = createAsyncThunk<
return data;
},
);
+
+export const checkAuthStatusAction = createAsyncThunk(
+ 'user/checkAuth',
+ async (_arg, {dispatch, extra: api}) => {
+ try {
+ const {data} = await api.get(APIRoute.Login);
+ dispatch(setAuthStatus(AuthStatus.Auth));
+ dispatch(setUserData(data));
+ return data;
+ } catch {
+ dispatch(setAuthStatus(AuthStatus.NoAuth));
+ return null;
+ }
+ },
+);
+
+export const loginAction = createAsyncThunk(
+ 'user/login',
+ async ({login: email, password}, {dispatch, extra: api}) => {
+ const {data} = await api.post(APIRoute.Login, {email, password});
+ saveToken(data.token);
+ dispatch(setAuthStatus(AuthStatus.Auth));
+
+ dispatch(setUserData(data));
+ return data;
+ },
+);
+
+export const logoutAction = createAsyncThunk(
+ 'user/logout',
+ async (_arg, {dispatch, extra: api}) => {
+ await api.delete(APIRoute.Logout);
+ dropToken();
+ dispatch(setAuthStatus(AuthStatus.NoAuth));
+ dispatch(setUserData(null));
+ },
+);
diff --git a/src/store/index.tsx b/src/store/index.tsx
index 208f4d1..75ec8d7 100644
--- a/src/store/index.tsx
+++ b/src/store/index.tsx
@@ -15,4 +15,5 @@ export const store = configureStore({
});
export type RootState = ReturnType;
+
export type AppDispatch = typeof store.dispatch;
diff --git a/src/store/reducer.ts b/src/store/reducer.ts
index 4b55f39..d9899ca 100644
--- a/src/store/reducer.ts
+++ b/src/store/reducer.ts
@@ -1,11 +1,19 @@
-import {CityObject} from '../const.ts';
+import {AuthStatus, CityObject} from '../const.ts';
import {OffersShort} from '../types/offers/offer-short.ts';
import {createReducer} from '@reduxjs/toolkit';
-import {setActiveCity, setHoverCardId, setOffers, setOffersLoading, setSortingOrder} from './acions.ts';
+import {
+ setActiveCity,
+ setAuthStatus,
+ setHoverCardId,
+ setOffers,
+ setOffersLoading,
+ setSortingOrder, setUserData
+} from './acions.ts';
import {City} from '../types/city.ts';
import {Nullable} from '../types/nullable.ts';
import {SortingOrder} from '../types/sorting-order.ts';
-import {fetchOffers} from './api-actions.ts';
+import {fetchOffersAction} from './api-actions.ts';
+import {UserData} from '../types/user-data.ts';
type AppState = {
activeCity: City;
@@ -13,6 +21,8 @@ type AppState = {
hoverCardId: Nullable;
sortingOrder: SortingOrder;
isOffersLoading: boolean;
+ authorizationStatus: AuthStatus;
+ userData: Nullable;
}
const initialState : AppState = {
@@ -21,6 +31,8 @@ const initialState : AppState = {
hoverCardId: null,
sortingOrder: SortingOrder.popular,
isOffersLoading: false,
+ authorizationStatus: AuthStatus.Unknown,
+ userData: null,
};
export const reducer = createReducer(initialState, (builder) => {
@@ -37,10 +49,16 @@ export const reducer = createReducer(initialState, (builder) => {
.addCase(setOffers, (state, action) => {
state.offers = action.payload;
})
- .addCase(fetchOffers.fulfilled, (state, action) => {
+ .addCase(fetchOffersAction.fulfilled, (state, action) => {
state.offers = action.payload;
})
.addCase(setOffersLoading, (state, action) => {
state.isOffersLoading = action.payload;
+ })
+ .addCase(setAuthStatus, (state, action) => {
+ state.authorizationStatus = action.payload;
+ })
+ .addCase(setUserData, (state, action) => {
+ state.userData = action.payload;
});
});
diff --git a/src/types/auth-data.ts b/src/types/auth-data.ts
new file mode 100644
index 0000000..b8d2025
--- /dev/null
+++ b/src/types/auth-data.ts
@@ -0,0 +1,4 @@
+export type AuthData = {
+ login: string;
+ password: string;
+};
diff --git a/src/types/offers/offer-detailed.ts b/src/types/offers/offer-detailed.ts
index 55d70e3..c43dbce 100644
--- a/src/types/offers/offer-detailed.ts
+++ b/src/types/offers/offer-detailed.ts
@@ -1,11 +1,11 @@
-import {User} from '../user.ts';
+import {UserData} from '../user-data.ts';
import {OfferShort} from './offer-short.ts';
export type OfferDetailed = Omit & {
description: string;
bedrooms: number;
goods: string[];
- host: User;
+ host: UserData;
images: string[];
maxAdults: number;
}
diff --git a/src/types/review.ts b/src/types/review.ts
index 6cbdb57..97a2db3 100644
--- a/src/types/review.ts
+++ b/src/types/review.ts
@@ -1,9 +1,9 @@
-import {User} from './user.ts';
+import {UserData} from './user-data.ts';
export type Review = {
id: string;
date: string;
- user: User;
+ user: UserData;
comment: string;
rating: number;
}
diff --git a/src/types/user-data.ts b/src/types/user-data.ts
new file mode 100644
index 0000000..1fdc9a3
--- /dev/null
+++ b/src/types/user-data.ts
@@ -0,0 +1,12 @@
+export type UserData = {
+ name: string;
+ avatarUrl: string;
+ isPro: boolean;
+ email: string;
+ token: string;
+}
+
+export type AuthData = {
+ login: string;
+ password: string;
+}
diff --git a/src/types/user.ts b/src/types/user.ts
deleted file mode 100644
index 9a1670e..0000000
--- a/src/types/user.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export type User = {
- name: string;
- avatarUrl: string;
- isPro: boolean;
-}