From d8345645e66c56a4b2aba29aeb98ef5a2dc3b331 Mon Sep 17 00:00:00 2001 From: "hani.mohammad" Date: Wed, 27 Nov 2024 00:25:28 +0300 Subject: [PATCH] add login and get token --- src/App.tsx | 21 ++++--- src/api-actions.ts | 62 ++++++++++++-------- src/components/Login/LoginPage.tsx | 91 ++++++++++++++++++++---------- src/const.ts | 7 +++ src/reducer.ts | 2 + src/store/user.ts | 37 ++++++++++++ src/store/userselector.ts | 22 ++++++++ src/token.ts | 2 +- src/types/types.ts | 23 ++++++++ 9 files changed, 201 insertions(+), 66 deletions(-) create mode 100644 src/store/user.ts create mode 100644 src/store/userselector.ts diff --git a/src/App.tsx b/src/App.tsx index f50619d..6d41378 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,5 @@ -import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; +import { BrowserRouter, Routes, Route } from 'react-router-dom'; +import { HelmetProvider } from 'react-helmet-async'; import MainPage from './components/MainPage/MainPage'; import Favorite from './components/Favorites/Favorite'; import LoginPage from './components/Login/LoginPage'; @@ -12,13 +13,15 @@ export const App: React.FC = () => { const offers = useAppSelector((state) => state.offerPage); const cities = useAppSelector((state) => state.Cities); return ( - - - } /> - } /> - } /> - } /> - - + + + + } /> + } /> + } /> + } /> + + + ); }; diff --git a/src/api-actions.ts b/src/api-actions.ts index ecc918f..17c2a8d 100644 --- a/src/api-actions.ts +++ b/src/api-actions.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-shadow */ import { AxiosInstance } from 'axios'; import { createAsyncThunk } from '@reduxjs/toolkit'; import { AppDispatch, State } from './types/types'; @@ -5,9 +6,10 @@ 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 { UserAuth, LoginAuth } from './types/types'; //import {UserData} from '../types/user-data'; import { createAPI } from './api'; +import { dropToken, saveToken } from './token'; export const api = createAPI(); export const fetchOfferObjectAction = createAsyncThunk< @@ -33,28 +35,38 @@ export const checkAuthAction = createAsyncThunk('user/login', async ({ email, password }, { extra: api }) => { + const { data } = await api.post(APIRoute.Login, { + email, + password, + }); + saveToken(data.token); + //dispatch(redirectToRoute(AppRoute.Result)); + return { + name: data.name, + avatarUrl: data.avatarUrl, + isPro: data.isPro, + email: data.email, + token: data.token, + }; +}); -export const loginAction = createAsyncThunk( - 'user/login', - async ({login: email, password}, {dispatch, extra: api}) => { - const {data: {token}} = await api.post(APIRoute.Login, {email, password}); - saveToken(token); - dispatch(redirectToRoute(AppRoute.Result)); - }, -); - -export const logoutAction = createAsyncThunk( - 'user/logout', - async (_arg, {extra: api}) => { - await api.delete(APIRoute.Logout); - dropToken(); - }, -);*/ +export const logout = createAsyncThunk< + void, + undefined, + { + dispatch: AppDispatch; + state: State; + extra: AxiosInstance; + } +>('user/logout', async (_arg, { extra: api }) => { + await api.delete(APIRoute.Logout); + dropToken(); +}); diff --git a/src/components/Login/LoginPage.tsx b/src/components/Login/LoginPage.tsx index 0e1673d..b08df25 100644 --- a/src/components/Login/LoginPage.tsx +++ b/src/components/Login/LoginPage.tsx @@ -1,59 +1,88 @@ -export default function LoginPage () { +import { Helmet } from 'react-helmet-async'; +import { Link, useNavigate } from 'react-router-dom'; +import { AuthorizationStatus } from '../../const.ts'; +import { AppRoute } from '../../types/types.ts'; +import { FormEvent, useEffect, useState, ChangeEvent } from 'react'; +import { useAppDispatch, useAppSelector } from '../../hooks/index.tsx'; +import { login } from '../../api-actions.ts'; +import { getAuthStatus, getUserDataLoadingStatus } from '../../store/userselector.ts'; + +export default function LoginPage(): JSX.Element { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + function handleEmailChange(e: ChangeEvent) { + setEmail(e.target.value); + } + + function handlePasswordChange(e: ChangeEvent) { + setPassword(e.target.value); + } + + const authorizationStatus = useAppSelector(getAuthStatus); + const dispatch = useAppDispatch(); + const navigate = useNavigate(); + const isLoading = useAppSelector(getUserDataLoadingStatus); + + useEffect(() => { + if (authorizationStatus === AuthorizationStatus.Auth) { + navigate(AppRoute.Main); + } + }, [authorizationStatus, navigate]); + + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + + if (email !== '' && password !== '') { + dispatch(login({ + email: email, + password: password, + })) + .then((response) => { + if(response.meta.requestStatus !== 'rejected' && !isLoading) { + setEmail(''); + setPassword(''); + } + }); + } + }; + return (
+ + 6 Cities: Login or Register +
- - 6 cities logo - + + 6 cities logo +
-

Sign in

-
+
- +
- +
- +
diff --git a/src/const.ts b/src/const.ts index 485f151..24511a3 100644 --- a/src/const.ts +++ b/src/const.ts @@ -6,4 +6,11 @@ export const URL_MARKER_CURRENT = export enum APIRoute { Offers = '/offers', + Login = '/login', + Logout = '/logout', +} +export enum AuthorizationStatus { + Auth = 'AUTH', + NoAuth = 'NO_AUTH', + Unknown = 'UNKNOWN', } diff --git a/src/reducer.ts b/src/reducer.ts index f11e7bd..40c4f5d 100644 --- a/src/reducer.ts +++ b/src/reducer.ts @@ -2,6 +2,7 @@ import { combineReducers, createReducer } from '@reduxjs/toolkit'; import { City, OfferObject } from './types/types'; import { changeCity, AddOffer, loadOffers } from './action'; import { offerPage } from './store/offer-data'; +import { user } from './store/user'; import { CITYLIST } from './mock/cities'; import { offers } from './mock/offers'; @@ -36,4 +37,5 @@ export const rootReducer = combineReducers({ Cities: reducer, currentCity: reducer, offerPage: offerPage.reducer, + user: user.reducer, }); diff --git a/src/store/user.ts b/src/store/user.ts new file mode 100644 index 0000000..1869259 --- /dev/null +++ b/src/store/user.ts @@ -0,0 +1,37 @@ +import { createSlice } from '@reduxjs/toolkit'; +import { AuthorizationStatus } from '../const'; +import { AuthorizationSlice } from '../types/types'; +import { login, logout } from '../api-actions'; + +const initialState: AuthorizationSlice = { + authorizationStatus: AuthorizationStatus.Unknown, + userData: null, + postError: false, + userDataLoadingStatus: false, +}; + +export const user = createSlice({ + name: 'user', + initialState, + reducers: {}, + extraReducers(builder) { + builder + .addCase(login.fulfilled, (state, action) => { + state.authorizationStatus = AuthorizationStatus.Auth; + state.userData = action.payload; + state.userDataLoadingStatus = false; + state.postError = false; + }) + .addCase(login.rejected, (state) => { + state.authorizationStatus = AuthorizationStatus.NoAuth; + state.postError = true; + }) + .addCase(logout.fulfilled, (state) => { + state.authorizationStatus = AuthorizationStatus.NoAuth; + state.userData = null; + }) + .addCase(login.pending, (state) => { + state.userDataLoadingStatus = true; + }); + }, +}); diff --git a/src/store/userselector.ts b/src/store/userselector.ts new file mode 100644 index 0000000..925dc1e --- /dev/null +++ b/src/store/userselector.ts @@ -0,0 +1,22 @@ +import { createSelector } from '@reduxjs/toolkit'; +import { AuthorizationSlice, State } from '../types/types'; + +export const getAuthStatus = createSelector( + (state: State) => state['user'], + (state: AuthorizationSlice) => state.authorizationStatus +); + +export const getUserData = createSelector( + (state: State) => state['user'], + (state: AuthorizationSlice) => state.userData +); + +export const getUserPostError = createSelector( + (state: State) => state['user'], + (state: AuthorizationSlice) => state.postError +); + +export const getUserDataLoadingStatus = createSelector( + (state: State) => state['user'], + (state: AuthorizationSlice) => state.userDataLoadingStatus +); diff --git a/src/token.ts b/src/token.ts index ee97828..55603cd 100644 --- a/src/token.ts +++ b/src/token.ts @@ -1,4 +1,4 @@ -const AUTH_TOKEN_KEY_NAME = 'guess-melody-token'; +const AUTH_TOKEN_KEY_NAME = 'sixcities-token'; export type Token = string; diff --git a/src/types/types.ts b/src/types/types.ts index 2b03a01..d4245e7 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -1,4 +1,5 @@ import { store } from '../store'; +import { AuthorizationStatus } from '../const'; export type City = { title: string; lat: number; @@ -8,6 +9,13 @@ export type OfferData = { offer: OfferObject[] | null; offerPageStatus: boolean; }; + +export type AuthorizationSlice = { + authorizationStatus: AuthorizationStatus; + userData: UserAuth | null; + postError: boolean; + userDataLoadingStatus: boolean; +}; export type Point = { title: string; lat: number; @@ -60,12 +68,27 @@ export type UserReview = { comment: string; date: Date; }; +export type UserObject = { + name: string; + avatarUrl: string; + isPro: boolean; +}; export const enum SortName { popular = 'popular', lowToHigh = 'lowToHigh', highToLow = 'highToLow', topRated = 'topRated', } + +export type UserAuth = UserObject & { + email: string; + token: string; +}; + +export type LoginAuth = { + email: string; + password: string; +}; export type Points = Point[]; export type State = ReturnType;