From 795703b266f1fdb5852bb58bb24f0b4957a0e2c3 Mon Sep 17 00:00:00 2001
From: mayonnaise <90057279+Mayanzev@users.noreply.github.com>
Date: Tue, 30 Apr 2024 01:48:35 +0300
Subject: [PATCH 1/2] module7-task1
---
src/components/app/app.tsx | 19 ++++--
src/components/city-list/city-list.tsx | 16 +++--
.../error-message/error-message.css | 9 +++
.../error-message/error-message.tsx | 13 ++++
src/components/map/map.tsx | 8 +--
src/components/offer-card/offer-card.tsx | 7 ++-
src/const.ts | 59 +++++++++++++++++++
src/hooks/use-map.tsx | 6 +-
src/index.tsx | 5 ++
src/mocks/cities.ts | 34 -----------
src/mocks/offers.ts | 59 -------------------
src/mocks/points.ts | 20 -------
.../favorites-screen/favorites-screen.tsx | 10 ++--
src/pages/loading-screen/loading-screen.tsx | 7 +++
src/pages/main-screen/main-screen.tsx | 11 ++--
src/pages/offer-screen/offer-screen.tsx | 31 +++++-----
src/services/api.ts | 54 +++++++++++++++++
src/services/process-error-handle.ts | 8 +++
src/services/token.ts | 16 +++++
src/store/action.ts | 23 ++++++--
src/store/api-actions.ts | 32 ++++++++++
src/store/index.ts | 13 +++-
src/store/reducer.ts | 34 +++++++----
src/types/city.ts | 4 --
src/types/location.ts | 10 ++++
src/types/offer.ts | 17 +++---
src/types/point.ts | 4 --
src/utils.ts | 2 +
28 files changed, 333 insertions(+), 198 deletions(-)
create mode 100644 src/components/error-message/error-message.css
create mode 100644 src/components/error-message/error-message.tsx
delete mode 100644 src/mocks/cities.ts
delete mode 100644 src/mocks/offers.ts
delete mode 100644 src/mocks/points.ts
create mode 100644 src/pages/loading-screen/loading-screen.tsx
create mode 100644 src/services/api.ts
create mode 100644 src/services/process-error-handle.ts
create mode 100644 src/services/token.ts
create mode 100644 src/store/api-actions.ts
delete mode 100644 src/types/city.ts
create mode 100644 src/types/location.ts
delete mode 100644 src/types/point.ts
diff --git a/src/components/app/app.tsx b/src/components/app/app.tsx
index 58c794b..87bd2cd 100644
--- a/src/components/app/app.tsx
+++ b/src/components/app/app.tsx
@@ -6,11 +6,20 @@ import NotFoundScreen from '../../pages/not-found-screen/not-found-screen.tsx';
import OfferScreen from '../../pages/offer-screen/offer-screen.tsx';
import PrivateRoute from '../private-route/private-route.tsx';
import { AppRoute, AuthorizationStatus } from '../../const.ts';
-import { Offer } from '../../types/offer';
-import { OFFERS } from '../../mocks/offers.ts';
+import { useAppSelector } from '../../hooks/index.ts';
+import LoadingScreen from '../../pages/loading-screen/loading-screen.tsx';
+
function App(): JSX.Element {
- const favourites: Offer[] = OFFERS.filter((o) => o.isFavorite);
+
+ const isQuestionsDataLoading = useAppSelector((state) => state.isQuestionsDataLoading);
+
+ if (isQuestionsDataLoading) {
+ return (
+
+ );
+ }
+
return (
@@ -20,12 +29,12 @@ function App(): JSX.Element {
path={AppRoute.Favorites}
element={
-
+
}
/>
} />
- } />
+ } />
);
diff --git a/src/components/city-list/city-list.tsx b/src/components/city-list/city-list.tsx
index 5f062c1..5877faf 100644
--- a/src/components/city-list/city-list.tsx
+++ b/src/components/city-list/city-list.tsx
@@ -1,13 +1,11 @@
-import { City } from '../../types/city';
-import { useAppDispatch } from '../../hooks';
+import { City } from '../../types/location';
+import { useAppDispatch, useAppSelector } from '../../hooks';
import { changeCity } from '../../store/action';
-import { CITIES } from '../../mocks/cities';
+import { CITIES } from '../../const';
-type CityListProps = {
- chosenCity: City;
-}
-function CityList({chosenCity}: CityListProps): JSX.Element {
+function CityList(): JSX.Element {
+ const chosenCity = useAppSelector((state) => state.city);
const dispatch = useAppDispatch();
const handleCityChange = (city: City) => {
dispatch(changeCity(city));
@@ -15,12 +13,12 @@ function CityList({chosenCity}: CityListProps): JSX.Element {
return(
diff --git a/src/pages/loading-screen/loading-screen.tsx b/src/pages/loading-screen/loading-screen.tsx
new file mode 100644
index 0000000..4d5f781
--- /dev/null
+++ b/src/pages/loading-screen/loading-screen.tsx
@@ -0,0 +1,7 @@
+function LoadingScreen(): JSX.Element {
+ return (
+ Loading ...
+ );
+ }
+
+ export default LoadingScreen;
\ No newline at end of file
diff --git a/src/pages/main-screen/main-screen.tsx b/src/pages/main-screen/main-screen.tsx
index 96993a8..5261bfe 100644
--- a/src/pages/main-screen/main-screen.tsx
+++ b/src/pages/main-screen/main-screen.tsx
@@ -7,9 +7,10 @@ import CityList from '../../components/city-list/city-list';
import CardsSortingOptions from '../../components/cards-sorting-options/cards-sorting-options';
function MainScreen(): JSX.Element {
- const [city, offers] = useAppSelector((state) => [state.city, state.offers]);
- const chosenOffers = offers.filter((offer) => offer.city === city);
- const points = chosenOffers.map((offer) => offer.point);
+ const city = useAppSelector((state) => state.city);
+ const offers = useAppSelector((state) => state.offers);
+ const chosenOffers = offers.filter((offer) => offer.city.name === city.name);
+ const points = chosenOffers.map((offer) => offer.location);
const favoriteOffers = offers.filter((offer) => offer.isFavorite);
return (
@@ -48,14 +49,14 @@ function MainScreen(): JSX.Element {
Cities
Places
- {chosenOffers.length} places to stay in {city.title}
+ {chosenOffers.length} places to stay in {city.name}
diff --git a/src/pages/offer-screen/offer-screen.tsx b/src/pages/offer-screen/offer-screen.tsx
index 2f8a840..471020c 100644
--- a/src/pages/offer-screen/offer-screen.tsx
+++ b/src/pages/offer-screen/offer-screen.tsx
@@ -1,17 +1,14 @@
import { Link } from 'react-router-dom';
import ReviewsList from '../../components/reviews-list/reviews-list';
-import { OFFERS } from '../../mocks/offers';
-import { Offer } from '../../types/offer';
-import { POINTS } from '../../mocks/points';
import Map from '../../components/map/map';
import OfferList from '../../components/offer-list/offer-list';
-import { typeOfCardList } from '../../utils';
+import { ratingPercentage, typeOfCardList } from '../../utils';
+import { useAppSelector } from '../../hooks';
+import { REVIEWS } from '../../mocks/reviews';
-type OfferScreenProps = {
- offer: Offer;
-}
-function OfferScreen({offer}: OfferScreenProps): JSX.Element {
+function OfferScreen(): JSX.Element {
+ const [offer, offers] = useAppSelector((state) => [state.chosenOffer, state.offers]);
return (
@@ -71,14 +68,14 @@ function OfferScreen({offer}: OfferScreenProps): JSX.Element {
- {offer.isPremium ? (
+ {offer?.isPremium ? (
Premium
) : null}
- {offer.title}
+ {offer?.title}
-
+
Rating
-
{offer.rating}
+
{offer?.rating}
-
- {offer.type}
+ {offer?.type}
-
3 Bedrooms
@@ -106,7 +103,7 @@ function OfferScreen({offer}: OfferScreenProps): JSX.Element {
- €{offer.price}
+ €{offer?.price}
night
@@ -163,17 +160,17 @@ function OfferScreen({offer}: OfferScreenProps): JSX.Element {
-
+
Other places in the neighbourhood
-
+
diff --git a/src/services/api.ts b/src/services/api.ts
new file mode 100644
index 0000000..ef5b976
--- /dev/null
+++ b/src/services/api.ts
@@ -0,0 +1,54 @@
+import axios, { AxiosError, AxiosInstance, AxiosResponse } from "axios";
+import { getToken } from "./token";
+import { StatusCodes } from "http-status-codes";
+import { processErrorHandle } from "./process-error-handle";
+
+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 => {
+ const api = axios.create({
+ baseURL: BACKEND_URL,
+ timeout: REQUEST_TIMEOUT,
+ });
+
+ api.interceptors.request.use(
+ (config) => {
+ 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);
+
+ processErrorHandle(detailMessage.message);
+ }
+
+ throw error;
+ }
+ );
+
+ return api;
+};
diff --git a/src/services/process-error-handle.ts b/src/services/process-error-handle.ts
new file mode 100644
index 0000000..e0d2ef3
--- /dev/null
+++ b/src/services/process-error-handle.ts
@@ -0,0 +1,8 @@
+import {store} from '../store';
+import {setError} from '../store/action';
+import {clearErrorAction} from '../store/api-actions';
+
+export const processErrorHandle = (message: string): void => {
+ store.dispatch(setError(message));
+ store.dispatch(clearErrorAction());
+};
\ No newline at end of file
diff --git a/src/services/token.ts b/src/services/token.ts
new file mode 100644
index 0000000..9c7a9e8
--- /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);
+};
\ No newline at end of file
diff --git a/src/store/action.ts b/src/store/action.ts
index 30000ad..366ca6f 100644
--- a/src/store/action.ts
+++ b/src/store/action.ts
@@ -1,8 +1,7 @@
import { createAction } from '@reduxjs/toolkit';
-import { City } from '../types/city';
-import { Point } from '../types/point';
-
-export const getOffers = createAction('OFFERS_GET');
+import { City } from '../types/location';
+import { Point } from '../types/location';
+import { Offer } from '../types/offer';
export const changeCity = createAction('CITY_CHANGE', (value: City) => ({
payload: value
@@ -15,3 +14,19 @@ export const changeSortOptions = createAction('CHANGE_SORT_OPTIONS', (value: str
export const changeHighlightedMarker = createAction('CHANGE_HIGHLIGHTED_MARKER', (value: Point | undefined) => ({
payload: value
}));
+
+export const loadOffers = createAction('LOAD_OFFERS', (value: Offer[]) => ({
+ payload: value
+}));
+
+export const changeChosenOffer = createAction('CHANGE_CHOSEN_OFFER', (value:Offer) => ({
+ payload: value
+}));
+
+export const setQuestionsDataLoadingStatus = createAction('SET_QUESTIONS_DATA_LOADING_STATUS', (value: boolean) => ({
+ payload: value
+}));
+
+export const setError = createAction('SET_ERROR', (value: string | null) => ({
+ payload: value
+}));
diff --git a/src/store/api-actions.ts b/src/store/api-actions.ts
new file mode 100644
index 0000000..44c7ca6
--- /dev/null
+++ b/src/store/api-actions.ts
@@ -0,0 +1,32 @@
+import { createAsyncThunk } from "@reduxjs/toolkit";
+import { AppDispatch, State } from "../types/state";
+import { AxiosInstance } from "axios";
+import { loadOffers, setError, setQuestionsDataLoadingStatus } from "./action";
+import { Offer } from "../types/offer";
+import { APIRoute, TIMEOUT_SHOW_ERROR } from "../const";
+
+import {store} from './';
+
+export const clearErrorAction = createAsyncThunk(
+ 'CLEAR_ERROR_ACTION',
+ () => {
+ setTimeout(
+ () => store.dispatch(setError(null)),
+ TIMEOUT_SHOW_ERROR,
+ );
+ },
+);
+
+export const fetchOffersAction = createAsyncThunk(
+ 'FETCH_OFFERS_ACTION',
+ async (_arg, {dispatch, extra: api}) => {
+ dispatch(setQuestionsDataLoadingStatus(true));
+ const {data} = await api.get(APIRoute.Offers);
+ dispatch(setQuestionsDataLoadingStatus(false));
+ dispatch(loadOffers(data));
+ },
+ );
\ No newline at end of file
diff --git a/src/store/index.ts b/src/store/index.ts
index e741183..44e24d6 100644
--- a/src/store/index.ts
+++ b/src/store/index.ts
@@ -1,4 +1,15 @@
import { configureStore } from '@reduxjs/toolkit';
import { reducer } from './reducer';
+import { createAPI } from '../services/api';
-export const store = configureStore({reducer});
+export const api = createAPI();
+
+export const store = configureStore({
+ reducer,
+ middleware: (getDefaultMiddleware) =>
+ getDefaultMiddleware({
+ thunk: {
+ extraArgument: api,
+ },
+ })
+});
diff --git a/src/store/reducer.ts b/src/store/reducer.ts
index f131dd1..70300df 100644
--- a/src/store/reducer.ts
+++ b/src/store/reducer.ts
@@ -1,11 +1,10 @@
import { createReducer } from '@reduxjs/toolkit';
-import { CITIES } from '../mocks/cities';
-import { OFFERS } from '../mocks/offers';
-import { City } from '../types/city';
+import { City } from '../types/location';
import { Offer } from '../types/offer';
-import { changeCity, changeHighlightedMarker, changeSortOptions, getOffers } from './action';
+import { changeChosenOffer, changeCity, changeHighlightedMarker, changeSortOptions, loadOffers, setError, setQuestionsDataLoadingStatus } from './action';
import { filters } from '../utils';
-import { Point } from '../types/point';
+import { Point } from '../types/location';
+import { CITIES } from '../const';
type StateType = {
@@ -13,20 +12,23 @@ type StateType = {
offers: Offer[];
sortType: string;
highlightedMarker?: Point;
+ chosenOffer: Offer | undefined;
+ isQuestionsDataLoading: boolean;
+ error: string | null;
}
const initialState: StateType = {
city: CITIES[0],
- offers: OFFERS,
+ offers: [],
sortType: filters.POPULAR,
- highlightedMarker: undefined
+ highlightedMarker: undefined,
+ chosenOffer: undefined,
+ isQuestionsDataLoading: false,
+ error: null
};
const reducer = createReducer(initialState, (builder) => {
builder
- .addCase(getOffers, (state) => {
- state.offers = OFFERS;
- })
.addCase(changeCity, (state, action) => {
state.city = action.payload;
})
@@ -35,6 +37,18 @@ const reducer = createReducer(initialState, (builder) => {
})
.addCase(changeHighlightedMarker, (state, action) => {
state.highlightedMarker = action.payload;
+ })
+ .addCase(loadOffers, (state, action) => {
+ state.offers = action.payload;
+ })
+ .addCase(changeChosenOffer, (state, action) => {
+ state.chosenOffer = action.payload;
+ })
+ .addCase(setQuestionsDataLoadingStatus, (state, action) => {
+ state.isQuestionsDataLoading = action.payload;
+ })
+ .addCase(setError, (state, action) => {
+ state.error = action.payload;
});
});
diff --git a/src/types/city.ts b/src/types/city.ts
deleted file mode 100644
index a809991..0000000
--- a/src/types/city.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import { Point } from '../types/point';
-export type City = {
- title: string;
-} & Point;
diff --git a/src/types/location.ts b/src/types/location.ts
new file mode 100644
index 0000000..a60f947
--- /dev/null
+++ b/src/types/location.ts
@@ -0,0 +1,10 @@
+export type Point = {
+ latitude: number;
+ longitude: number;
+ zoom: number;
+}
+
+export type City = {
+ name: string;
+ location: Point
+}
diff --git a/src/types/offer.ts b/src/types/offer.ts
index 9811de6..bc6822d 100644
--- a/src/types/offer.ts
+++ b/src/types/offer.ts
@@ -1,17 +1,14 @@
-import { Point } from './point';
-import { Review } from '../types/review';
-import { City } from './city';
+import { Point, City } from './location';
export type Offer = {
id: string;
- image: string[];
- isPremium: boolean;
- price: number;
title: string;
type: string;
- isFavorite: boolean;
- rating: number;
- reviews: Review[];
+ price: number;
city: City;
- point: Point;
+ location: Point;
+ isFavorite: boolean
+ isPremium: boolean
+ rating: number
+ previewImage: string
};
diff --git a/src/types/point.ts b/src/types/point.ts
deleted file mode 100644
index 04a0b11..0000000
--- a/src/types/point.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export type Point = {
- lat: number;
- lng: number;
- };
diff --git a/src/utils.ts b/src/utils.ts
index cd9dea5..10145d3 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -37,3 +37,5 @@ export const getSortedOffers = (
return offers;
}
};
+
+export const ratingPercentage = (rating: number) => `${(rating / 5) * 100}%`
\ No newline at end of file
From 5b88998aa0262137516ddc880dc3c9596a5d663d Mon Sep 17 00:00:00 2001
From: mayonnaise <90057279+Mayanzev@users.noreply.github.com>
Date: Tue, 30 Apr 2024 02:03:51 +0300
Subject: [PATCH 2/2] fix
---
src/components/error-message/error-message.tsx | 2 +-
src/const.ts | 4 ++--
src/pages/loading-screen/loading-screen.tsx | 12 ++++++------
src/pages/offer-screen/offer-screen.tsx | 2 +-
src/services/api.ts | 10 +++++-----
src/services/process-error-handle.ts | 2 +-
src/services/token.ts | 2 +-
src/store/api-actions.ts | 14 +++++++-------
src/store/index.ts | 14 +++++++-------
src/types/location.ts | 2 +-
src/types/offer.ts | 8 ++++----
src/utils.ts | 2 +-
12 files changed, 37 insertions(+), 37 deletions(-)
diff --git a/src/components/error-message/error-message.tsx b/src/components/error-message/error-message.tsx
index a1ed6ac..93d568b 100644
--- a/src/components/error-message/error-message.tsx
+++ b/src/components/error-message/error-message.tsx
@@ -10,4 +10,4 @@ function ErrorMessage(): JSX.Element | null {
}
-export default ErrorMessage;
\ No newline at end of file
+export default ErrorMessage;
diff --git a/src/const.ts b/src/const.ts
index 64bdd7c..361c404 100644
--- a/src/const.ts
+++ b/src/const.ts
@@ -1,4 +1,4 @@
-import { City } from "./types/location";
+import { City } from './types/location';
export enum AppRoute {
Main = '/',
@@ -74,4 +74,4 @@ export const CITIES: City[] = [
},
];
-export const TIMEOUT_SHOW_ERROR = 2000;
\ No newline at end of file
+export const TIMEOUT_SHOW_ERROR = 2000;
diff --git a/src/pages/loading-screen/loading-screen.tsx b/src/pages/loading-screen/loading-screen.tsx
index 4d5f781..b886023 100644
--- a/src/pages/loading-screen/loading-screen.tsx
+++ b/src/pages/loading-screen/loading-screen.tsx
@@ -1,7 +1,7 @@
function LoadingScreen(): JSX.Element {
- return (
- Loading ...
- );
- }
-
- export default LoadingScreen;
\ No newline at end of file
+ return (
+ Loading ...
+ );
+}
+
+export default LoadingScreen;
diff --git a/src/pages/offer-screen/offer-screen.tsx b/src/pages/offer-screen/offer-screen.tsx
index 471020c..562c593 100644
--- a/src/pages/offer-screen/offer-screen.tsx
+++ b/src/pages/offer-screen/offer-screen.tsx
@@ -164,7 +164,7 @@ function OfferScreen(): JSX.Element {
-
diff --git a/src/services/api.ts b/src/services/api.ts
index ef5b976..35f6851 100644
--- a/src/services/api.ts
+++ b/src/services/api.ts
@@ -1,7 +1,7 @@
-import axios, { AxiosError, AxiosInstance, AxiosResponse } from "axios";
-import { getToken } from "./token";
-import { StatusCodes } from "http-status-codes";
-import { processErrorHandle } from "./process-error-handle";
+import axios, { AxiosError, AxiosInstance, AxiosResponse } from 'axios';
+import { getToken } from './token';
+import { StatusCodes } from 'http-status-codes';
+import { processErrorHandle } from './process-error-handle';
type DetailMessageType = {
type: string;
@@ -26,7 +26,7 @@ export const createAPI = (): AxiosInstance => {
});
api.interceptors.request.use(
- (config) => {
+ (config) => {
const token = getToken();
if (token && config.headers) {
diff --git a/src/services/process-error-handle.ts b/src/services/process-error-handle.ts
index e0d2ef3..db11914 100644
--- a/src/services/process-error-handle.ts
+++ b/src/services/process-error-handle.ts
@@ -5,4 +5,4 @@ import {clearErrorAction} from '../store/api-actions';
export const processErrorHandle = (message: string): void => {
store.dispatch(setError(message));
store.dispatch(clearErrorAction());
-};
\ No newline at end of file
+};
diff --git a/src/services/token.ts b/src/services/token.ts
index 9c7a9e8..df439d4 100644
--- a/src/services/token.ts
+++ b/src/services/token.ts
@@ -13,4 +13,4 @@ export const saveToken = (token: Token): void => {
export const dropToken = (): void => {
localStorage.removeItem(AUTH_TOKEN_KEY_NAME);
-};
\ No newline at end of file
+};
diff --git a/src/store/api-actions.ts b/src/store/api-actions.ts
index 44c7ca6..4cffb9f 100644
--- a/src/store/api-actions.ts
+++ b/src/store/api-actions.ts
@@ -1,9 +1,9 @@
-import { createAsyncThunk } from "@reduxjs/toolkit";
-import { AppDispatch, State } from "../types/state";
-import { AxiosInstance } from "axios";
-import { loadOffers, setError, setQuestionsDataLoadingStatus } from "./action";
-import { Offer } from "../types/offer";
-import { APIRoute, TIMEOUT_SHOW_ERROR } from "../const";
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import { AppDispatch, State } from '../types/state';
+import { AxiosInstance } from 'axios';
+import { loadOffers, setError, setQuestionsDataLoadingStatus } from './action';
+import { Offer } from '../types/offer';
+import { APIRoute, TIMEOUT_SHOW_ERROR } from '../const';
import {store} from './';
@@ -29,4 +29,4 @@ export const fetchOffersAction = createAsyncThunk
- getDefaultMiddleware({
- thunk: {
- extraArgument: api,
- },
- })
+ reducer,
+ middleware: (getDefaultMiddleware) =>
+ getDefaultMiddleware({
+ thunk: {
+ extraArgument: api,
+ },
+ })
});
diff --git a/src/types/location.ts b/src/types/location.ts
index a60f947..a532220 100644
--- a/src/types/location.ts
+++ b/src/types/location.ts
@@ -6,5 +6,5 @@ export type Point = {
export type City = {
name: string;
- location: Point
+ location: Point;
}
diff --git a/src/types/offer.ts b/src/types/offer.ts
index bc6822d..7c922a1 100644
--- a/src/types/offer.ts
+++ b/src/types/offer.ts
@@ -7,8 +7,8 @@ export type Offer = {
price: number;
city: City;
location: Point;
- isFavorite: boolean
- isPremium: boolean
- rating: number
- previewImage: string
+ isFavorite: boolean;
+ isPremium: boolean;
+ rating: number;
+ previewImage: string;
};
diff --git a/src/utils.ts b/src/utils.ts
index 10145d3..4b7cf60 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -38,4 +38,4 @@ export const getSortedOffers = (
}
};
-export const ratingPercentage = (rating: number) => `${(rating / 5) * 100}%`
\ No newline at end of file
+export const ratingPercentage = (rating: number) => `${(rating / 5) * 100}%`;