diff --git a/client/src/App.tsx b/client/src/App.tsx index 82fc9b7..23e38c3 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,22 +1,21 @@ import React, { useContext, useEffect } from 'react'; +import { createBrowserRouter, RouteObject, RouterProvider } from 'react-router-dom'; +import AuthContext from './context/AuthContext'; -// Styles -import './styles/custom.scss'; - -// Interfaces -import { createBrowserRouter, RouterProvider } from 'react-router-dom'; -import Landing from './views/Landing'; +import BrowserRoutes from './routes'; import ProtectedRoute from './routes/ProtectedRoute'; import Layout from './layout/PrivateLayout'; -import browserRoutes from './routes'; -import NotFound from './views/NotFound'; -import AuthContext from './context/AuthContext'; + +import Landing from './views/Landing'; import Login from './views/Login'; +import NotFound from './views/NotFound'; + +import './styles/custom.scss'; const App: React.FC = () => { const { signed, checkCurrentAuthUser } = useContext(AuthContext); - const notSignedRouter = createBrowserRouter([ + const notSignedRouter: RouteObject[] = [ { path: '/', element: @@ -29,38 +28,42 @@ const App: React.FC = () => { path: '*', element: } - ]); + ]; - const signedRouter = createBrowserRouter([ + const signedRouter: RouteObject[] = [ { - path: '/', + path: '/', // ROUTES.ROOT='/' element: , children: [ { element: , - children: browserRoutes + children: BrowserRoutes } ] }, { - path: '*', + path: '*', // ROUTES.ALL_ROUTES='*' element: } - ]); + ]; - const getBrowserRoutes = () => { + const getBrowserRouter = () => { if (signed) { - return signedRouter; + console.log('app signed'); + return createBrowserRouter(signedRouter); } - return notSignedRouter; + console.log('app not signed'); + return createBrowserRouter(notSignedRouter); }; + const browserRouter = getBrowserRouter(); + useEffect(() => { checkCurrentAuthUser(window.location.pathname); }, []); return ( - + ); }; diff --git a/client/src/api-service/apiConfig.ts b/client/src/api-service/apiConfig.ts index 5dcd540..e81b1b3 100644 --- a/client/src/api-service/apiConfig.ts +++ b/client/src/api-service/apiConfig.ts @@ -1,36 +1,74 @@ +import { API_TOKEN } from '../app-constants/app-constants'; import { env } from '../env'; -import { User } from '../types/User'; +import { SigninResponse } from '../types/SigninResponse'; const server = env.VITE_BACKEND_SERVER; /** * */ -function privateSessionFake(signed: boolean): Promise { - return new Promise((resolve, reject) => { - if (signed) { - resolve({ - name: 'User', - email: 'email@domain.com' - }); +async function privateSessionState(signed: boolean): Promise { + const tokenState = localStorage.getItem(API_TOKEN); + + if (signed && tokenState) { + const response = await fetch(ApiConfig.refreshTokenUrl, { + method: 'GET', + mode: 'cors', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${tokenState}` + } + }); + + if (response.ok) { + const data = await response.json(); + localStorage.setItem(API_TOKEN, data.token); + return { + token: data.token + }; } else { - reject(); + console.error(response); } - }); + } + return Promise.reject(); } const ApiConfig = { - login: `${server}/login`, + signinUrl: `${server}/auth/signin`, - loginFake: async (): Promise => new Promise((resolve) => { - resolve(); - }), + refreshTokenUrl: `${server}/rest/user-sessions/refresh`, + + signin: async (email: string, password: string): Promise => { + try { + const response = await fetch(ApiConfig.signinUrl, { + method: 'POST', + mode: 'cors', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ email, password }) + }); + + if (response.ok) { + const data = await response.json(); + localStorage.setItem(API_TOKEN, data.token); + return { + token: data.token + }; + } else { + console.error('signin error', response); + } + } + catch (error) { + console.error(error); + } + }, logoutFake: async (): Promise => new Promise((resolve) => { resolve(); }), - currentSessionFake: privateSessionFake + currentSessionState: privateSessionState }; export default ApiConfig; diff --git a/client/src/app-constants/app-constants.ts b/client/src/app-constants/app-constants.ts index 6175102..19fca9f 100644 --- a/client/src/app-constants/app-constants.ts +++ b/client/src/app-constants/app-constants.ts @@ -1 +1,3 @@ export const REDIRECT_PATH = 'TASKNOTE-REDIRECT'; +export const API_TOKEN = 'TASKNOTE-TOKEN'; +export const USER_DATA = 'TASKNOTE-USER'; diff --git a/client/src/context/AuthContext.ts b/client/src/context/AuthContext.ts index 9bc9a09..ad0fc7a 100644 --- a/client/src/context/AuthContext.ts +++ b/client/src/context/AuthContext.ts @@ -5,7 +5,7 @@ export interface AuthContextData { signed: boolean; user: User | undefined; checkCurrentAuthUser: (pathname: string) => Promise; - signIn: () => void; + signIn: (email: string, password: string) => void; signOut: () => void; isAdmin: boolean; } diff --git a/client/src/context/AuthProvider.tsx b/client/src/context/AuthProvider.tsx index 7ff7529..6d5d1e9 100644 --- a/client/src/context/AuthProvider.tsx +++ b/client/src/context/AuthProvider.tsx @@ -4,7 +4,8 @@ import { User } from '../types/User'; import { env } from '../env'; import ApiConfig from '../api-service/apiConfig'; import AuthContext, { AuthContextData } from './AuthContext'; -import { REDIRECT_PATH } from '../app-constants/app-constants'; +import { API_TOKEN, REDIRECT_PATH, USER_DATA } from '../app-constants/app-constants'; +import { SigninResponse } from '../types/SigninResponse'; interface Props { children: React.ReactNode; @@ -16,13 +17,13 @@ const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }: Pro const [isAdmin, setIsAdmin] = useState(false); const [intervalInstance, setIntervalInstance] = useState(null); - const fetchCurrentSession = async (pathname: string): Promise => { + const fetchCurrentSession = async (pathname: string): Promise => { try { - const currentUser = await ApiConfig.currentSessionFake(signed); - if (currentUser) { + const bearerToken: SigninResponse | undefined = await ApiConfig.currentSessionState(signed); + if (bearerToken && bearerToken.token) { setSigned(true); } - return currentUser; + return bearerToken; } catch (e) { if (e instanceof Error) { console.warn(e.message); @@ -38,28 +39,35 @@ const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }: Pro return undefined; }; - const updateUserSession = (userPriv: User) => { - localStorage.setItem('TaskNote-token', userPriv.email); + const updateUserSession = (userPriv: User | null, bearerToken: string) => { + if (userPriv) { + localStorage.setItem(USER_DATA, JSON.stringify(userPriv)); + } + localStorage.setItem(API_TOKEN, bearerToken); }; const checkCurrentAuthUser = async (pathname: string): Promise => { - const currentUser = await fetchCurrentSession(pathname); - if (currentUser) { - updateUserSession(currentUser); - setUser(currentUser); + console.log('checkCurrentAuthUser', pathname); + const bearerToken: SigninResponse | undefined = await fetchCurrentSession(pathname); + if (bearerToken && bearerToken.token) { + console.log('checkCurrentAuthUser token', bearerToken); + updateUserSession(null, bearerToken.token); } }; - const signIn = async (): Promise => { - const appEnv = env.VITE_ENV || 'dev'; - await ApiConfig.loginFake(); - setSigned(true); - const currentUser: User = { - name: `Ricardo ${appEnv}`, - email: 'ricardompcampos@gmail.com' - }; + const signIn = async (email: string, password: string): Promise => { + const bearerToken: SigninResponse | undefined = await ApiConfig.signin(email, password); + if (bearerToken && bearerToken.token) { + const currentUser: User = { + email + }; - setUser(currentUser); + setSigned(true); + setUser(currentUser); + updateUserSession(currentUser, bearerToken.token); + return Promise.resolve(); + } + return Promise.reject(); }; const signOut = async (): Promise => { @@ -74,11 +82,12 @@ const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }: Pro localStorage.clear(); }; - const refreshTokenPvt = async () => { - const currentUser = await fetchCurrentSession('/'); - if (currentUser) { - updateUserSession(currentUser); + const refreshTokenPvt = async (): Promise => { + const bearerToken: SigninResponse | undefined = await fetchCurrentSession('/'); + if (bearerToken) { + updateUserSession(null, bearerToken.token); } + return Promise.resolve(); }; // 2 minutes diff --git a/client/src/layout/PrivateLayout/index.tsx b/client/src/layout/PrivateLayout/index.tsx index 9048d41..a89c4b2 100644 --- a/client/src/layout/PrivateLayout/index.tsx +++ b/client/src/layout/PrivateLayout/index.tsx @@ -8,6 +8,7 @@ import Footer from '../../components/Footer'; * */ function Layout() { + console.log('Layout'); return ( <>
diff --git a/client/src/routes/index.tsx b/client/src/routes/index.tsx index 79eda30..25dfd5e 100644 --- a/client/src/routes/index.tsx +++ b/client/src/routes/index.tsx @@ -5,7 +5,7 @@ import Home from '../views/Home'; import About from '../views/About'; import NotFound from '../views/NotFound'; -const browserRoutes: RouteObject[] = [ +const BrowserRoutes: RouteObject[] = [ { path: '/', element: ( @@ -38,4 +38,4 @@ const browserRoutes: RouteObject[] = [ } ]; -export default browserRoutes; +export default BrowserRoutes; diff --git a/client/src/types/SigninResponse.ts b/client/src/types/SigninResponse.ts new file mode 100644 index 0000000..ce51a2b --- /dev/null +++ b/client/src/types/SigninResponse.ts @@ -0,0 +1,3 @@ +export type SigninResponse = { + token: string +} diff --git a/client/src/types/User.ts b/client/src/types/User.ts index ed349b9..0696b4f 100644 --- a/client/src/types/User.ts +++ b/client/src/types/User.ts @@ -1,4 +1,3 @@ export type User = { - name: string; email: string; } diff --git a/client/src/views/Home/index.tsx b/client/src/views/Home/index.tsx index 4b1f73e..15aeed1 100644 --- a/client/src/views/Home/index.tsx +++ b/client/src/views/Home/index.tsx @@ -4,6 +4,8 @@ import React from 'react'; * */ function Home() { + console.log('This is home!'); + return (

This is home!

); diff --git a/client/src/views/Landing/index.tsx b/client/src/views/Landing/index.tsx index 1b04833..6cc17f2 100644 --- a/client/src/views/Landing/index.tsx +++ b/client/src/views/Landing/index.tsx @@ -1,20 +1,35 @@ -import React from 'react'; +import React, { useContext, useEffect } from 'react'; import { Button } from 'react-bootstrap'; import './styles.scss'; import { useNavigate } from 'react-router-dom'; +import AuthContext from '../../context/AuthContext'; /** * */ function Landing() { + const { signed, checkCurrentAuthUser } = useContext(AuthContext); const navigate = useNavigate(); + const handleLogin = () => { + console.log('Landing signed', signed); + if (signed) { + navigate('/home'); + } else { + navigate('/signin'); + } + }; + + useEffect(() => { + checkCurrentAuthUser(window.location.pathname); + }, []); + return ( <>

This is landing page

diff --git a/client/src/views/Login/index.tsx b/client/src/views/Login/index.tsx index 68e9473..fb91988 100644 --- a/client/src/views/Login/index.tsx +++ b/client/src/views/Login/index.tsx @@ -1,7 +1,7 @@ import React, { useContext, useEffect, useState } from 'react'; -import { Button, Form } from 'react-bootstrap'; -import { FeedbackType } from 'react-bootstrap/esm/Feedback'; +import { Alert, Button, Col, Form } from 'react-bootstrap'; import AuthContext from '../../context/AuthContext'; +import { redirect } from 'react-router-dom'; /** * Created the Login component. @@ -9,98 +9,63 @@ import AuthContext from '../../context/AuthContext'; function Login() { const { signIn } = useContext(AuthContext); const [validated, setValidated] = useState(true); - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); - const [emailValidType, setEmailValidType] = useState('valid'); - const [emailValidMsg, setEmailValidMsg] = useState('Looks good!'); - const [passwordValidType, setPasswordValidType] = useState('valid'); - const [passwordValidMsg, setPasswordValidMsg] = useState('Looks good!'); + const [formInvalid, setFormInvalid] = useState(false); - const handleSubmit = (event: React.FormEvent) => { + const handleSubmit = async (event: React.FormEvent) => { event.preventDefault(); event.stopPropagation(); setValidated(true); const form = event.currentTarget; if (form.checkValidity() === false) { - if (email.length === 0) { - setEmailValidType('invalid'); - setEmailValidMsg('Please type your email!'); - } else { - setEmailValidType('valid'); - setEmailValidMsg('Looks good!'); - } - - if (password.length === 0) { - setPasswordValidType('invalid'); - setPasswordValidMsg('Please type your password!'); - } else { - setPasswordValidType('valid'); - setPasswordValidMsg('Looks good!'); - } - + console.log('something is invalid!'); + setFormInvalid(true); return; } - setEmailValidType('valid'); - setEmailValidMsg('Looks good!'); - - setPasswordValidType('valid'); - setPasswordValidMsg('Looks good!'); - signIn(); + setFormInvalid(false); + console.log(`email=${form.email.value}, password=${form.password.value}`); + await signIn(form.email.value, form.password.value); + console.log('finished!? login component!?'); + redirect("/home"); }; - const resetForm = () => { - setValidated(false); - setEmail(''); - setEmailValidType('valid'); - setPassword(''); - setPasswordValidType('valid'); - }; - - useEffect(() => {}, [validated]); + useEffect(() => {}, [formInvalid]); return ( <>

This is login page

- + Email address setEmail(e.target.value)} - isInvalid={emailValidType === 'invalid'} /> - - {emailValidMsg} - - + Password setPassword(e.target.value)} - isInvalid={passwordValidType === 'invalid'} /> - - {passwordValidMsg} - - + + {formInvalid ? ( + + Something is wrong! Check your email and password! + + ) : null} ); }