feat: add real login - wip

issue #46
This commit is contained in:
Ricardo Campos
2024-09-19 10:49:16 -03:00
parent 24f858e6a0
commit ea5c563b20
12 changed files with 159 additions and 122 deletions
+23 -20
View File
@@ -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: <Landing />
@@ -29,38 +28,42 @@ const App: React.FC = () => {
path: '*',
element: <NotFound />
}
]);
];
const signedRouter = createBrowserRouter([
const signedRouter: RouteObject[] = [
{
path: '/',
path: '/', // ROUTES.ROOT='/'
element: <ProtectedRoute />,
children: [
{
element: <Layout />,
children: browserRoutes
children: BrowserRoutes
}
]
},
{
path: '*',
path: '*', // ROUTES.ALL_ROUTES='*'
element: <NotFound />
}
]);
];
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 (
<RouterProvider router={getBrowserRoutes()} />
<RouterProvider router={browserRouter} />
);
};
+53 -15
View File
@@ -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<User | undefined> {
return new Promise((resolve, reject) => {
if (signed) {
resolve({
name: 'User',
email: 'email@domain.com'
});
async function privateSessionState(signed: boolean): Promise<SigninResponse | undefined> {
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<void> => new Promise((resolve) => {
resolve();
}),
refreshTokenUrl: `${server}/rest/user-sessions/refresh`,
signin: async (email: string, password: string): Promise<SigninResponse | undefined> => {
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<void> => new Promise((resolve) => {
resolve();
}),
currentSessionFake: privateSessionFake
currentSessionState: privateSessionState
};
export default ApiConfig;
@@ -1 +1,3 @@
export const REDIRECT_PATH = 'TASKNOTE-REDIRECT';
export const API_TOKEN = 'TASKNOTE-TOKEN';
export const USER_DATA = 'TASKNOTE-USER';
+1 -1
View File
@@ -5,7 +5,7 @@ export interface AuthContextData {
signed: boolean;
user: User | undefined;
checkCurrentAuthUser: (pathname: string) => Promise<void>;
signIn: () => void;
signIn: (email: string, password: string) => void;
signOut: () => void;
isAdmin: boolean;
}
+33 -24
View File
@@ -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<boolean>(false);
const [intervalInstance, setIntervalInstance] = useState<NodeJS.Timeout | null>(null);
const fetchCurrentSession = async (pathname: string): Promise<User | undefined> => {
const fetchCurrentSession = async (pathname: string): Promise<SigninResponse | undefined> => {
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<void> => {
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<void> => {
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<void> => {
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<void> => {
@@ -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<void> => {
const bearerToken: SigninResponse | undefined = await fetchCurrentSession('/');
if (bearerToken) {
updateUserSession(null, bearerToken.token);
}
return Promise.resolve();
};
// 2 minutes
@@ -8,6 +8,7 @@ import Footer from '../../components/Footer';
*
*/
function Layout() {
console.log('Layout');
return (
<>
<Header />
+2 -2
View File
@@ -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;
+3
View File
@@ -0,0 +1,3 @@
export type SigninResponse = {
token: string
}
-1
View File
@@ -1,4 +1,3 @@
export type User = {
name: string;
email: string;
}
+2
View File
@@ -4,6 +4,8 @@ import React from 'react';
*
*/
function Home() {
console.log('This is home!');
return (
<h1>This is home!</h1>
);
+17 -2
View File
@@ -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 (
<>
<h1>This is landing page</h1>
<Button
type="button"
onClick={() => navigate('/signin')}
onClick={handleLogin}
>
SignIn
</Button>
+22 -57
View File
@@ -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<boolean>(true);
const [email, setEmail] = useState<string>('');
const [password, setPassword] = useState<string>('');
const [emailValidType, setEmailValidType] = useState<FeedbackType | undefined>('valid');
const [emailValidMsg, setEmailValidMsg] = useState<string>('Looks good!');
const [passwordValidType, setPasswordValidType] = useState<FeedbackType | undefined>('valid');
const [passwordValidMsg, setPasswordValidMsg] = useState<string>('Looks good!');
const [formInvalid, setFormInvalid] = useState<boolean>(false);
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
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 (
<>
<h1>This is login page</h1>
<Form noValidate validated={validated} onSubmit={handleSubmit}>
<Form.Group className="mb-3" controlId="formBasicEmail">
<Form.Group as={Col} md="4" className="mb-3" controlId="formBasicEmail">
<Form.Label>Email address</Form.Label>
<Form.Control
required
type="email"
name="email"
placeholder="Enter email"
value={email}
onChange={(e) => setEmail(e.target.value)}
isInvalid={emailValidType === 'invalid'}
/>
<Form.Control.Feedback type={emailValidType}>
{emailValidMsg}
</Form.Control.Feedback>
</Form.Group>
<Form.Group className="mb-3" controlId="formBasicPassword">
<Form.Group as={Col} md="4" className="mb-3" controlId="formBasicPassword">
<Form.Label>Password</Form.Label>
<Form.Control
required
type="password"
name="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
isInvalid={passwordValidType === 'invalid'}
/>
<Form.Control.Feedback type={passwordValidType}>
{passwordValidMsg}
</Form.Control.Feedback>
</Form.Group>
<Button variant="primary" type="submit">
Login
</Button>
<Button variant="secondary" type="button" onClick={() => resetForm()}>
Reset
</Button>
</Form>
{formInvalid ? (
<Alert variant={"danger"}>
Something is wrong! Check your email and password!
</Alert>
) : null}
</>
);
}