Skip to content

Commit

Permalink
Merge pull request #10 from Nawwar14/module7-task1
Browse files Browse the repository at this point in the history
  • Loading branch information
keksobot authored Nov 24, 2024
2 parents d51eb04 + 8ebac7c commit e6b8b2e
Show file tree
Hide file tree
Showing 22 changed files with 311 additions and 41 deletions.
14 changes: 9 additions & 5 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,23 @@ module.exports = {
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
"htmlacademy/react-typescript",
'htmlacademy/react-typescript',
],
parser: '@typescript-eslint/parser',
parserOptions: { ecmaVersion: 'latest', sourceType: 'module', project: 'tsconfig.json' },
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
project: 'tsconfig.json',
},
settings: { react: { version: 'detect' } },
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': 'warn',
},
overrides: [
{
files: [ '*test*' ],
rules: { '@typescript-eslint/unbound-method': 'off' }
files: ['*test*'],
rules: { '@typescript-eslint/unbound-method': 'off' },
},
],
}
};
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@
"vite": "4.4.11",
"vitest": "0.34.6"
},
"eslintConfig": {
"rules": {
"no-console": "off"
}
},
"browserslist": {
"production": [
">0.2%",
Expand Down
12 changes: 6 additions & 6 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@ import LoginPage from './components/Login/LoginPage';
import Offer from './components/Offer/Offer';
import { useAppSelector } from './hooks';
import { REVIEWERS } from './mock/reviewers';

//import LoadingScreen from './components/loading-screen/loading-screen';

export const App: React.FC = () => {
const currentCity = useAppSelector((state) => state.currentCity);
const offers = useAppSelector((state) => state.offers);
const cities = useAppSelector((state) => state.cities);
const offers = useAppSelector((state) => state.offerPage);
const cities = useAppSelector((state) => state.Cities);
return (
<Router>
<Routes>
<Route path="/" element={<MainPage offers={offers} currentCity={currentCity} cities={cities}/>} />
<Route path="/" element={<MainPage currentCity={currentCity.currentCity} cities={cities.cities}/>} />
<Route path="/login" element={<LoginPage />} />
<Route path="/offer/:id" element={<Offer reviews={REVIEWERS} offers={offers} currentCity={currentCity}/>} />
<Route path="/favorites" element={<Favorite offers={offers} />} />
<Route path="/offer/:id" element={<Offer reviews={REVIEWERS} offers={offers.offer} currentCity={currentCity.currentCity}/>} />
<Route path="/favorites" element={<Favorite offers={offers.offer} />} />
</Routes>
</Router>
);
Expand Down
2 changes: 2 additions & 0 deletions src/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ import { OfferObject } from './types/types';
export const changeCity = createAction<string>('ChangeCity');

export const AddOffer = createAction<OfferObject[]>('AddOffer');

export const loadOffers = createAction<OfferObject[]>('data/fetchOffers');
60 changes: 60 additions & 0 deletions src/api-actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { AxiosInstance } from 'axios';
import { createAsyncThunk } from '@reduxjs/toolkit';
import { AppDispatch, State } from './types/types';
import { OfferObject } from './types/types';
//import {redirectToRoute} from './action';
//import { saveToken, dropToken } from './token';
import { APIRoute } from './const';
//import {AuthData} from '../types/auth-data';
//import {UserData} from '../types/user-data';
import { createAPI } from './api';

export const api = createAPI();
export const fetchOfferObjectAction = createAsyncThunk<
OfferObject[],
undefined,
{
dispatch: AppDispatch;
state: State;
extra: AxiosInstance;
}
>('data/fetchOffers', async () => {
const { data } = await api.get<OfferObject[]>(APIRoute.Offers);
return data;
});
/*
export const checkAuthAction = createAsyncThunk<void, undefined, {
dispatch: AppDispatch;
state: State;
extra: AxiosInstance;
}>(
'user/checkAuth',
async (_arg, {extra: api}) => {
await api.get(APIRoute.Login);
},
);
export const loginAction = createAsyncThunk<void, AuthData, {
dispatch: AppDispatch;
state: State;
extra: AxiosInstance;
}>(
'user/login',
async ({login: email, password}, {dispatch, extra: api}) => {
const {data: {token}} = await api.post<UserData>(APIRoute.Login, {email, password});
saveToken(token);
dispatch(redirectToRoute(AppRoute.Result));
},
);
export const logoutAction = createAsyncThunk<void, undefined, {
dispatch: AppDispatch;
state: State;
extra: AxiosInstance;
}>(
'user/logout',
async (_arg, {extra: api}) => {
await api.delete(APIRoute.Logout);
dropToken();
},
);*/
52 changes: 52 additions & 0 deletions src/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import axios, {
AxiosInstance,
//AxiosRequestConfig,
InternalAxiosRequestConfig,
AxiosResponse,
AxiosError,
} from 'axios';
//import AxiosRequestConfig from 'axios';
import { StatusCodes } from 'http-status-codes';
import { getToken } from './token';

const BACKEND_URL = 'https://14.design.htmlacademy.pro/six-cities';
const REQUEST_TIMEOUT = 5000;
type DetailMessageType = {
type: string;
message: string;
};

const StatusCodeMapping: Record<number, boolean> = {
[StatusCodes.BAD_REQUEST]: true,
[StatusCodes.UNAUTHORIZED]: true,
[StatusCodes.NOT_FOUND]: true,
};

const shouldDisplayError = (response: AxiosResponse) =>
!!StatusCodeMapping[response.status];
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<DetailMessageType>) => {
if (error.response && shouldDisplayError(error.response)) {
//const detailMessage = (error.response.data);
//toast.warn(detailMessage.message);
}

throw error;
}
);
return api;
};
4 changes: 2 additions & 2 deletions src/components/Favorites/Favorite.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import OfferCard from '../Offer/OfferCard';
import { OfferObject, CardCssNameList } from '../../types/types';

type FavoriteProps = {
offers: OfferObject[];
offers: OfferObject[] | null;
};
const Favorite = ({ offers }: FavoriteProps) => (
<div className="page">
Expand All @@ -12,7 +12,7 @@ const Favorite = ({ offers }: FavoriteProps) => (
<section className="favorites">
<h1 className="favorites__title">Saved listings</h1>
<div className="favorites__list">
{offers.map((offer) => (
{offers?.map((offer) => (
<OfferCard key={offer.id} offer={offer} cardcssname={CardCssNameList.favoritePlace} />
))}
</div>
Expand Down
34 changes: 20 additions & 14 deletions src/components/MainPage/MainPage.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,39 @@
import {FC} from 'react';
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import Spinner from '../spinner/spinner.tsx';
import OfferList from '../Offer/OfferList';
import { useAppDispatch } from '../../hooks';
import { OfferObject,AppRoute, City, CardCssNameList, SortName} from '../../types/types';
import { useAppDispatch,useAppSelector } from '../../hooks';
import { AppRoute, City, CardCssNameList, SortName} from '../../types/types';
import { changeCity } from '../../action';
import { ListCities } from '../../components/CityList/CityList';
import { FilterOffer } from '../FilterOffers/FilterOffer';
import { getLoadingOfferPage,getOffer } from '../../store/selector';
import { fetchOfferObjectAction } from '../../api-actions.ts';
import Map from '../Map/Map';
type MainPageProps = {
offers: OfferObject[];
currentCity: City;
cities: City[];
};
export const MainPage : FC<MainPageProps> = ({
offers,
currentCity,
cities,
}:MainPageProps) => {
const navigate = useNavigate();
const dispatch = useAppDispatch();

const isLoading = useAppSelector(getLoadingOfferPage);
const offers = useAppSelector(getOffer);
useEffect(() => {
dispatch(fetchOfferObjectAction());
}, [dispatch]);
const handleUserSelectCity = (cityName: string) => {
dispatch(changeCity(cityName));
// dispatch(fillOffers());
};
const [activeOffer, setActiveOffer] = useState<number | null>(null);

const [sortType, setSortType] = useState<SortName>(SortName.popular);
const sortedOffers = offers.filter((a) =>a.city.name === currentCity.title).slice().sort((a, b) => {
const sortedOffers = offers?.filter((a) =>a.city.name === currentCity.title).slice().sort((a, b) => {
switch (sortType) {
case SortName.lowToHigh:
return a.price - b.price;
Expand Down Expand Up @@ -85,18 +90,19 @@ export const MainPage : FC<MainPageProps> = ({
<div className="cities__places-container container">
<section className="cities__places places">
<h2 className="visually-hidden">Places</h2>
<b className="places__found">{offers.filter((a) =>a.city.name === currentCity.title).length} places to stay in {currentCity.title}</b>
<b className="places__found">{offers?.filter((a) =>a.city.name === currentCity.title).length} places to stay in {currentCity.title}</b>
<b className="places__found">
{sortedOffers.length} places to stay in {currentCity.title}
{sortedOffers?.length} places to stay in {currentCity.title}
</b>
<FilterOffer currentSort={sortType} onSortChange={setSortType} />
<div className="cities__places-list places__list tabs__content">
<OfferList offers={sortedOffers.filter((a) =>a.city.name === currentCity.title)} cardcssname={CardCssNameList.citiesList} setActiveOffer={setActiveOffer}/>
</div>
{ isLoading ?
<Spinner />
:
<><FilterOffer currentSort={sortType} onSortChange={setSortType} /><div className="cities__places-list places__list tabs__content"><OfferList offers={sortedOffers?.filter((a) => a.city.name === currentCity.title)} cardcssname={CardCssNameList.citiesList} setActiveOffer={setActiveOffer} /></div>
</>}
</section>
<div className="cities__right-section">
<section className="cities__map map">
<Map offers={sortedOffers.filter((a) =>a.city.name === currentCity.title)} selectedPoint={sortedOffers[0]} activeOffer={activeOffer} currentCity={currentCity} />
<Map offers={sortedOffers?.filter((a) =>a.city.name === currentCity.title)} selectedPoint={sortedOffers?.[0]} activeOffer={activeOffer} currentCity={currentCity} />
</section>
</div>
</div>
Expand Down
6 changes: 3 additions & 3 deletions src/components/Map/Map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ const activeCustomIcon = new Icon({
iconAnchor: [20, 40]
});
type MainPageProps = {
offers: OfferObject[];
offers: OfferObject[] | undefined;
currentCity: City;
selectedPoint: OfferObject;
selectedPoint: OfferObject | undefined;
activeOffer: number | null;
};

Expand All @@ -41,7 +41,7 @@ function Map(props: MainPageProps): JSX.Element {
}
});

offers.forEach((offer) => {
offers?.forEach((offer) => {
leaflet
.marker({
lat: offer.location.latitude,
Expand Down
8 changes: 4 additions & 4 deletions src/components/Offer/Offer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Map from '../Map/Map';
import { AppRoute, UserReview, OfferObject, City } from '../../types/types';
type OfferProps = {
reviews: UserReview[];
offers: OfferObject[];
offers: OfferObject[] | null;
currentCity: City;
};

Expand Down Expand Up @@ -192,10 +192,10 @@ export const Offer: React.FC<OfferProps> = ({
</div>
<section className="offer__map map">
<Map
offers={[...offers]}
selectedPoint={offers[1]}
offers={offers === null ? undefined : [...offers]}
selectedPoint={offers?.[1]}
currentCity={currentCity}
activeOffer={offers[1].id}
activeOffer={offers === null ? null : offers?.[1].id}
/>
</section>
</section>
Expand Down
4 changes: 2 additions & 2 deletions src/components/Offer/OfferList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { OfferObject } from '../../types/types';


type OfferListProps = {
offers: OfferObject[];
offers: OfferObject[] | undefined;
cardcssname: string;
setActiveOffer?: (id: number | null) => void;
};
Expand All @@ -14,7 +14,7 @@ const OfferList = ({ offers, cardcssname,setActiveOffer}: OfferListProps) => {

return (
<div className="cities__places-list places__list tabs__content">
{offers.map((offer) => (
{offers?.map((offer) => (
<div
key={offer.id}
onMouseEnter={() => setActiveOfferId(offer.id)}
Expand Down
8 changes: 8 additions & 0 deletions src/components/loading-screen/loading-screen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

function LoadingScreen(): JSX.Element {
return (
<p>Loading ...</p>
);
}

export default LoadingScreen;
35 changes: 35 additions & 0 deletions src/components/spinner/spinner.modules.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
.spinner-container {
display: flex;
}
.loader {
width: 48px;
height: 48px;
margin-left: auto;
margin-right: auto;
border-radius: 50%;
display: inline-block;
border-top: 4px solid #000;
border-right: 4px solid transparent;
box-sizing: border-box;
animation: rotation 1s linear infinite;
}
.loader::after {
content: '';
box-sizing: border-box;
position: absolute;
left: 0;
top: 0;
width: 48px;
height: 48px;
border-radius: 50%;
border-bottom: 4px solid #4481c3;
border-left: 4px solid transparent;
}
@keyframes rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
5 changes: 5 additions & 0 deletions src/components/spinner/spinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import './spinner.modules.css';

export default function Spinner(): JSX.Element {
return <div className="spinner-container"><span className="loader"></span></div>;
}
4 changes: 4 additions & 0 deletions src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@ export const URL_MARKER_DEFAULT =

export const URL_MARKER_CURRENT =
'https://assets.htmlacademy.ru/content/intensive/javascript-1/demo/interactive-map/main-pin.svg';

export enum APIRoute {
Offers = '/offers',
}
Loading

0 comments on commit e6b8b2e

Please sign in to comment.