Added AuthContext. Protected routes and functionality based on isAuthenticated property

This commit is contained in:
Paweł Malak
2021-10-18 16:12:06 +02:00
parent f501941a22
commit b1241d8927
12 changed files with 140 additions and 60 deletions

View File

@@ -1,20 +1,25 @@
import { BrowserRouter, Switch, Route } from 'react-router-dom'; import { BrowserRouter, Switch, Route } from 'react-router-dom';
import { Navbar } from './components/Navigation/Navbar'; import { Navbar } from './components/Navigation/Navbar';
import { Editor, Home, Snippet, Snippets } from './containers'; import { Editor, Home, Snippet, Snippets, Auth } from './containers';
import { SnippetsContextProvider } from './store'; import { AuthContextProvider, SnippetsContextProvider } from './store';
import { ProtectedRoute } from './utils';
export const App = () => { export const App = () => {
return ( return (
<BrowserRouter> <BrowserRouter>
<SnippetsContextProvider> <AuthContextProvider>
<Navbar /> <SnippetsContextProvider>
<Switch> <Navbar />
<Route exact path='/' component={Home} /> <Switch>
<Route path='/snippets' component={Snippets} /> <Route exact path='/' component={Home} />
<Route path='/snippet/:id' component={Snippet} /> <Route path='/snippets' component={Snippets} />
<Route path='/editor/:id?' component={Editor} /> <Route path='/snippet/:id' component={Snippet} />
</Switch> <Route path='/snippet/:id' component={Snippet} />
</SnippetsContextProvider> <Route path='/auth' component={Auth} />
<ProtectedRoute path='/editor/:id?' component={Editor} />
</Switch>
</SnippetsContextProvider>
</AuthContextProvider>
</BrowserRouter> </BrowserRouter>
); );
}; };

View File

@@ -1,21 +1,34 @@
import { NavLink } from 'react-router-dom'; import { NavLink } from 'react-router-dom';
import { Route } from '../../typescript/interfaces'; import { Route } from '../../typescript/interfaces';
import { routes as clientRoutes } from './routes.json'; import { routes as clientRoutes } from './routes.json';
import { useContext } from 'react';
import { AuthContext } from '../../store';
export const Navbar = (): JSX.Element => { export const Navbar = (): JSX.Element => {
const routes = clientRoutes as Route[]; const routes = clientRoutes as Route[];
const { isAuthenticated } = useContext(AuthContext);
return ( return (
<nav className='navbar navbar-dark bg-dark navbar-expand'> <nav className='navbar navbar-dark bg-dark navbar-expand'>
<div className='container-fluid'> <div className='container-fluid'>
<ul className='navbar-nav'> <ul className='navbar-nav'>
{routes.map(({ name, dest }, idx) => ( {isAuthenticated
<li className='nav-item' key={idx}> ? routes.map((route, idx) => (
<NavLink exact to={dest} className='nav-link'> <li className='nav-item' key={idx}>
{name} <NavLink exact to={route.dest} className='nav-link'>
</NavLink> {route.name}
</li> </NavLink>
))} </li>
))
: routes
.filter(r => r.isPublic)
.map((route, idx) => (
<li className='nav-item' key={idx}>
<NavLink exact to={route.dest} className='nav-link'>
{route.name}
</NavLink>
</li>
))}
</ul> </ul>
</div> </div>
</nav> </nav>

View File

@@ -2,15 +2,18 @@
"routes": [ "routes": [
{ {
"name": "Home", "name": "Home",
"dest": "/" "dest": "/",
"isPublic": true
}, },
{ {
"name": "Snippets", "name": "Snippets",
"dest": "/snippets" "dest": "/snippets",
"isPublic": true
}, },
{ {
"name": "Editor", "name": "Editor",
"dest": "/editor" "dest": "/editor",
"isPublic": false
} }
] ]
} }

View File

@@ -1,6 +1,6 @@
import { useContext } from 'react'; import { Fragment, useContext } from 'react';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { SnippetsContext } from '../../store'; import { SnippetsContext, AuthContext } from '../../store';
import { Snippet } from '../../typescript/interfaces'; import { Snippet } from '../../typescript/interfaces';
import { dateParser } from '../../utils'; import { dateParser } from '../../utils';
import { Badge, Button, Card } from '../UI'; import { Badge, Button, Card } from '../UI';
@@ -27,14 +27,11 @@ export const SnippetDetails = (props: Props): JSX.Element => {
const history = useHistory(); const history = useHistory();
const { deleteSnippet, setSnippet } = useContext(SnippetsContext); const { deleteSnippet, setSnippet } = useContext(SnippetsContext);
const { isAuthenticated } = useContext(AuthContext);
const creationDate = dateParser(createdAt); const creationDate = dateParser(createdAt);
const updateDate = dateParser(updatedAt); const updateDate = dateParser(updatedAt);
// const copyHandler = () => {
// copy(code);
// };
return ( return (
<Card> <Card>
<h5 className='card-title d-flex align-items-center justify-content-between'> <h5 className='card-title d-flex align-items-center justify-content-between'>
@@ -74,27 +71,31 @@ export const SnippetDetails = (props: Props): JSX.Element => {
{/* ACTIONS */} {/* ACTIONS */}
<div className='d-grid g-2' style={{ rowGap: '10px' }}> <div className='d-grid g-2' style={{ rowGap: '10px' }}>
<Button {isAuthenticated && (
text='Delete' <Fragment>
color='danger' <Button
small text='Delete'
outline color='danger'
handler={() => deleteSnippet(id)} small
/> outline
handler={() => deleteSnippet(id)}
/>
<Button <Button
text='Edit' text='Edit'
color='secondary' color='secondary'
small small
outline outline
handler={() => { handler={() => {
setSnippet(id); setSnippet(id);
history.push({ history.push({
pathname: `/editor/${id}`, pathname: `/editor/${id}`,
state: { from: window.location.pathname } state: { from: window.location.pathname }
}); });
}} }}
/> />
</Fragment>
)}
<Button <Button
text='Copy raw url' text='Copy raw url'

View File

@@ -1,5 +1,5 @@
import { useContext } from 'react'; import { Fragment, useContext } from 'react';
import { SnippetsContext } from '../../store'; import { SnippetsContext, AuthContext } from '../../store';
import Icon from '@mdi/react'; import Icon from '@mdi/react';
import { mdiPin, mdiPinOutline } from '@mdi/js'; import { mdiPin, mdiPinOutline } from '@mdi/js';
@@ -10,15 +10,20 @@ interface Props {
export const SnippetPin = (props: Props): JSX.Element => { export const SnippetPin = (props: Props): JSX.Element => {
const { toggleSnippetPin } = useContext(SnippetsContext); const { toggleSnippetPin } = useContext(SnippetsContext);
const { isAuthenticated } = useContext(AuthContext);
const { id, isPinned } = props; const { id, isPinned } = props;
return ( return (
<div onClick={() => toggleSnippetPin(id)} className='cursor-pointer'> <Fragment>
{isPinned ? ( {isAuthenticated && (
<Icon path={mdiPin} size={0.8} color='#20c997' /> <div onClick={() => toggleSnippetPin(id)} className='cursor-pointer'>
) : ( {isPinned ? (
<Icon path={mdiPinOutline} size={0.8} color='#ced4da' /> <Icon path={mdiPin} size={0.8} color='#20c997' />
) : (
<Icon path={mdiPinOutline} size={0.8} color='#ced4da' />
)}
</div>
)} )}
</div> </Fragment>
); );
}; };

View File

@@ -0,0 +1,29 @@
import { useState, createContext, ReactNode } from 'react';
import { AuthContext as Context } from '../typescript/interfaces';
export const AuthContext = createContext<Context>({
isAuthenticated: false,
login: () => {}
});
interface Props {
children: ReactNode;
}
export const AuthContextProvider = (props: Props): JSX.Element => {
const login = async (formData: { email: string; password: string }) => {
console.table(formData);
};
const context: Context = {
isAuthenticated: false,
login
};
return (
<AuthContext.Provider value={context}>
{props.children}
</AuthContext.Provider>
);
};

View File

@@ -1,8 +1,9 @@
import { useState, createContext } from 'react'; import { useState, createContext, ReactNode } from 'react';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import axios from 'axios'; import axios from 'axios';
import { import {
Context, SnippetsContext as Context,
Snippet, Snippet,
Response, Response,
TagCount, TagCount,
@@ -27,7 +28,7 @@ export const SnippetsContext = createContext<Context>({
}); });
interface Props { interface Props {
children: JSX.Element | JSX.Element[]; children: ReactNode;
} }
export const SnippetsContextProvider = (props: Props): JSX.Element => { export const SnippetsContextProvider = (props: Props): JSX.Element => {
@@ -153,7 +154,7 @@ export const SnippetsContextProvider = (props: Props): JSX.Element => {
.catch(err => console.log(err)); .catch(err => console.log(err));
}; };
const context = { const context: Context = {
snippets, snippets,
searchResults, searchResults,
currentSnippet, currentSnippet,

View File

@@ -1 +1,2 @@
export * from './SnippetsContext'; export * from './SnippetsContext';
export * from './AuthContext';

View File

@@ -1,6 +1,6 @@
import { TagCount, NewSnippet, Snippet, SearchQuery } from '.'; import { TagCount, NewSnippet, Snippet, SearchQuery } from '.';
export interface Context { export interface SnippetsContext {
snippets: Snippet[]; snippets: Snippet[];
searchResults: Snippet[]; searchResults: Snippet[];
currentSnippet: Snippet | null; currentSnippet: Snippet | null;
@@ -15,3 +15,8 @@ export interface Context {
countTags: () => void; countTags: () => void;
searchSnippets: (query: SearchQuery) => void; searchSnippets: (query: SearchQuery) => void;
} }
export interface AuthContext {
isAuthenticated: boolean;
login: (formData: { email: string; password: string }) => void;
}

View File

@@ -1,4 +1,5 @@
export interface Route { export interface Route {
name: string; name: string;
dest: string; dest: string;
isPublic: boolean;
} }

View File

@@ -0,0 +1,15 @@
import { Redirect, Route, RouteProps } from 'react-router';
import { useContext } from 'react';
import { AuthContext } from '../store';
// interface Props extends RouteProps {}
export const ProtectedRoute = ({ ...rest }: RouteProps) => {
const { isAuthenticated } = useContext(AuthContext);
if (isAuthenticated) {
return <Route {...rest} />;
} else {
return <Redirect to='/auth' />;
}
};

View File

@@ -2,3 +2,4 @@ export * from './dateParser';
export * from './badgeColor'; export * from './badgeColor';
export * from './findLanguage'; export * from './findLanguage';
export * from './searchParser'; export * from './searchParser';
export * from './ProtectedRoute';