Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Истина где-то на сервере #10

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading