Skip to content

Commit

Permalink
Merge pull request #11 from Sayafarov/module7-task1
Browse files Browse the repository at this point in the history
  • Loading branch information
keksobot authored Jan 4, 2025
2 parents e87906e + f962786 commit ee28af6
Show file tree
Hide file tree
Showing 56 changed files with 1,418 additions and 483 deletions.
24 changes: 24 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/* eslint-env node */

module.exports = {
env: { browser: true, es2022: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
"htmlacademy/react-typescript",
],
parser: '@typescript-eslint/parser',
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' }
},
],
}
77 changes: 77 additions & 0 deletions public/css/extended.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
.spinner {
display: flex;
justify-content: center;
align-items: center;
flex-grow: 1;
}

.spinner__icon {
width: 48px;
height: 48px;
border: 5px solid #b9b9b9;
border-top: 5px solid transparent;
border-radius: 50%;
animation: spin 1s linear infinite;
}

@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}

.alert {
color: red;
margin: 10px;
padding: 10px;
border: 1px solid red;
border-radius: 5px;
background-color: #f8d7da;
text-align: center;
font-size: 16px;
font-weight: bold;
display: flex;
height: fit-content;
flex-grow: 1;
justify-content: center;
}

.login__error {
color: #f64747;
margin: 20px 10px 10px;
text-align: center;
font-size: 16px;
}

.review-send__error {
color: #f64747;
margin: 20px 10px 10px;
text-align: center;
font-size: 16px;
}

fieldset {
border: 0;
padding: 0.01em 0 0 0;
margin: 0;
min-width: 0;
}

.not-found {
display: flex;
justify-content: center;
align-items: center;
flex-grow: 1;
height: 100vh;
width: 100vw;
font-size: 24px;
flex-direction: column;
text-align: center;
}

.not-found__button {
font-size: 16px;
padding: 10px 20px;
border-radius: 5px;
background-color: #000;
color: #fff;
}
36 changes: 36 additions & 0 deletions src/api/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import axios, {AxiosError, AxiosInstance, InternalAxiosRequestConfig} from 'axios';
import { getToken } from './token';

const BASE_URL = 'https://14.design.htmlacademy.pro/six-cities';
const REQUEST_TIMEOUT = 5000;

class DetailMessageType {
}

export const createAPI = (): AxiosInstance => {
const api = axios.create({
baseURL: BASE_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>) => {
throw error;
}
);

return api;
};
16 changes: 16 additions & 0 deletions src/api/token.ts
Original file line number Diff line number Diff line change
@@ -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);
};
3 changes: 3 additions & 0 deletions src/components/alert/alert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function Alert({message}: {message: string}) {
return <div className="alert">{message}</div>;
}
23 changes: 16 additions & 7 deletions src/components/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,26 @@ import Favorites from '@pages/favorites/favorites.tsx';

import {AppRoute} from '@const/app-routes.ts';
import {PrivateRoute} from '@components/private-route/private-route.tsx';
import {AuthorizationStatus} from '@type/authorization-status.ts';
import {setOffers, setReviews} from '@store/action.ts';
import {checkAuth, getFavoriteOffers} from '@store/api-actions.ts';
import {useEffect} from 'react';
import {useAppDispatch, useAppSelector} from '@hooks/index.ts';
import {authStatusSelector} from '@store/user-data/selectors.ts';
import {Auth} from '@type/auth.ts';


function App(): JSX.Element {
const offers = useAppSelector((state) => state.offersList);
const reviews = useAppSelector((state) => state.reviews);
const dispatch = useAppDispatch();
dispatch(setOffers(offers));
dispatch(setReviews(reviews));
const authStatus = useAppSelector(authStatusSelector);

useEffect(() => {
dispatch(checkAuth());
}, [dispatch]);

useEffect(() => {
if (authStatus === Auth.Auth) {
dispatch(getFavoriteOffers());
}
}, [dispatch, authStatus]);

return (
<BrowserRouter>
Expand All @@ -34,7 +43,7 @@ function App(): JSX.Element {
<Route
path={AppRoute.Favorites}
element={
<PrivateRoute authorizationStatus={AuthorizationStatus.Auth}>
<PrivateRoute>
<Favorites/>
</PrivateRoute>
}
Expand Down
16 changes: 11 additions & 5 deletions src/components/cities-list/cities-list.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { useAppDispatch } from '@hooks/index';
import { setCity } from '@store/action';
import {City} from '@type/common.ts';
import {useAppDispatch, useAppSelector} from '@hooks/index';
import {City} from '@type/location.ts';
import {setCity} from '@store/main-page-data/main-page-data.ts';
import {memo} from 'react';
import {citySelector} from '@store/main-page-data/selectors.ts';

type CitiesListProps = {
cities: City[];
};

export function CitiesList({ cities }: CitiesListProps): JSX.Element {
function CitiesList({ cities }: CitiesListProps): JSX.Element {
const dispatch = useAppDispatch();
const currentCity = useAppSelector(citySelector);

const handleCityChange = (city: City) => {
dispatch(setCity(city));
Expand All @@ -21,11 +24,14 @@ export function CitiesList({ cities }: CitiesListProps): JSX.Element {
className="locations__item"
onClick={() => handleCityChange(city)}
>
<a className="locations__item-link tabs__item" href="#">
<a className={`locations__item-link tabs__item ${currentCity.name === city.name ? 'tabs__item--active' : ''}`} href="#">
<span>{city.name}</span>
</a>
</li>
))}
</ul>
);
}

const CitiesListMemo = memo(CitiesList);
export default CitiesListMemo;
65 changes: 49 additions & 16 deletions src/components/header/header.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,60 @@
import {memo} from 'react';
import {useAppDispatch, useAppSelector} from '@hooks/index.ts';
import {authStatusSelector, userSelector} from '@store/user-data/selectors.ts';
import {logout} from '@store/api-actions.ts';
import {Auth} from '@type/auth.ts';
import {Link} from 'react-router-dom';
import {AppRoute} from '@const/app-routes.ts';
import {favoritesSelector} from '@store/offers-data/selectors.ts';

function Header(): JSX.Element {
const dispatch = useAppDispatch();
const favorites = useAppSelector(favoritesSelector);
const favoritesCount = favorites.length;
const user = useAppSelector(userSelector);
const authStatus = useAppSelector(authStatusSelector);

const handleSignOut = () => {
dispatch(logout());
};

return (
<header className="header">
<div className="container">
<div className="header__wrapper">
<div className="header__left">
<a className="header__logo-link header__logo-link--active">
<Link className="header__logo-link" to={AppRoute.Main}>
<img className="header__logo" src="img/logo.svg" alt="6 cities logo" width="81" height="41" />
</a>
</Link>
</div>
<nav className="header__nav">
<ul className="header__nav-list">
<li className="header__nav-item user">
<a className="header__nav-link header__nav-link--profile" href="#">
<div className="header__avatar-wrapper user__avatar-wrapper">
</div>
<span className="header__user-name user__name">[email protected]</span>
<span className="header__favorite-count">3</span>
</a>
</li>
<li className="header__nav-item">
<a className="header__nav-link" href="#">
<span className="header__signout">Sign out</span>
</a>
</li>
{authStatus === Auth.Auth && user !== null ? (
<>
<li className="header__nav-item user">
<Link className="header__nav-link header__nav-link--profile" to={AppRoute.Favorites}>
<div className="header__avatar-wrapper user__avatar-wrapper">
<img className="user__avatar" src={user.avatarUrl} alt="avatar"/>
</div>
<span className="header__user-name user__name">{user.email}</span>
<span className="header__favorite-count">{favoritesCount}</span>
</Link>
</li>
<li className="header__nav-item">
<Link className="header__nav-link" to={AppRoute.Main} onClick={handleSignOut}>
<span className="header__signout">Sign out</span>
</Link>
</li>
</>)
: (
<li className="header__nav-item user">
<Link className="header__nav-link header__nav-link--profile" to={AppRoute.Login}>
<div className="header__avatar-wrapper user__avatar-wrapper">
</div>
<span className="header__login">Sign in</span>
</Link>
</li>
)}
</ul>
</nav>
</div>
Expand All @@ -31,4 +63,5 @@ function Header(): JSX.Element {
);
}

export default Header;
const HeaderMemo = memo(Header);
export default HeaderMemo;
8 changes: 4 additions & 4 deletions src/components/map/map.offer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ export function Map({offer, nearbyOffers}: MapProps): JSX.Element {
const markerLayer = layerGroup().addTo(map);
nearbyOffers.forEach((offer_) => {
const marker = new Marker({
lat: offer_.location.lt,
lng: offer_.location.lg
lat: offer_.location.latitude,
lng: offer_.location.longitude
});
marker.setIcon(defaultCustomIcon).addTo(map);
});
const marker = new Marker({
lat: offer.location.lt,
lng: offer.location.lg
lat: offer.location.latitude,
lng: offer.location.longitude
});
marker.setIcon(currentCustomIcon).addTo(map);

Expand Down
15 changes: 9 additions & 6 deletions src/components/map/map.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {useEffect, useRef} from 'react';
import {memo, useEffect, useRef} from 'react';
import {Icon, layerGroup, Marker} from 'leaflet';
import {City} from '@type/common.ts';
import {City} from '@type/location.ts';
import 'leaflet/dist/leaflet.css';
import {Offer} from '@type/offers.ts';
import {URL_MARKER_CURRENT, URL_MARKER_DEFAULT} from '@const/sources.ts';
Expand All @@ -9,7 +9,7 @@ import {useMap} from '@components/map/useMap.ts';
type MapProps = {
city: City;
offers: Offer[];
selectedOfferId?: string;
selectedOfferId: string | null;
};


Expand All @@ -25,7 +25,7 @@ const currentCustomIcon = new Icon({
iconAnchor: [20, 40]
});

export function Map({city, offers, selectedOfferId}: MapProps): JSX.Element {
function Map({city, offers, selectedOfferId}: MapProps): JSX.Element {
const mapRef = useRef(null);
const map = useMap(mapRef, city);

Expand All @@ -34,8 +34,8 @@ export function Map({city, offers, selectedOfferId}: MapProps): JSX.Element {
const markerLayer = layerGroup().addTo(map);
offers.forEach((offer) => {
const marker = new Marker({
lat: offer.location.lt,
lng: offer.location.lg
lat: offer.location.latitude,
lng: offer.location.longitude,
});
marker
.setIcon(
Expand All @@ -56,3 +56,6 @@ export function Map({city, offers, selectedOfferId}: MapProps): JSX.Element {
<div className="cities__map map" style={{height: '500px'}} ref={mapRef}></div>
);
}

const MapMemo = memo(Map);
export default MapMemo;
Loading

0 comments on commit ee28af6

Please sign in to comment.