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 { Navbar } from './components/Navigation/Navbar';
import { Editor, Home, Snippet, Snippets } from './containers';
import { SnippetsContextProvider } from './store';
import { Editor, Home, Snippet, Snippets, Auth } from './containers';
import { AuthContextProvider, SnippetsContextProvider } from './store';
import { ProtectedRoute } from './utils';
export const App = () => {
return (
<BrowserRouter>
<SnippetsContextProvider>
<Navbar />
<Switch>
<Route exact path='/' component={Home} />
<Route path='/snippets' component={Snippets} />
<Route path='/snippet/:id' component={Snippet} />
<Route path='/editor/:id?' component={Editor} />
</Switch>
</SnippetsContextProvider>
<AuthContextProvider>
<SnippetsContextProvider>
<Navbar />
<Switch>
<Route exact path='/' component={Home} />
<Route path='/snippets' component={Snippets} />
<Route path='/snippet/:id' component={Snippet} />
<Route path='/snippet/:id' component={Snippet} />
<Route path='/auth' component={Auth} />
<ProtectedRoute path='/editor/:id?' component={Editor} />
</Switch>
</SnippetsContextProvider>
</AuthContextProvider>
</BrowserRouter>
);
};

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
import { useContext } from 'react';
import { SnippetsContext } from '../../store';
import { Fragment, useContext } from 'react';
import { SnippetsContext, AuthContext } from '../../store';
import Icon from '@mdi/react';
import { mdiPin, mdiPinOutline } from '@mdi/js';
@@ -10,15 +10,20 @@ interface Props {
export const SnippetPin = (props: Props): JSX.Element => {
const { toggleSnippetPin } = useContext(SnippetsContext);
const { isAuthenticated } = useContext(AuthContext);
const { id, isPinned } = props;
return (
<div onClick={() => toggleSnippetPin(id)} className='cursor-pointer'>
{isPinned ? (
<Icon path={mdiPin} size={0.8} color='#20c997' />
) : (
<Icon path={mdiPinOutline} size={0.8} color='#ced4da' />
<Fragment>
{isAuthenticated && (
<div onClick={() => toggleSnippetPin(id)} className='cursor-pointer'>
{isPinned ? (
<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 axios from 'axios';
import {
Context,
SnippetsContext as Context,
Snippet,
Response,
TagCount,
@@ -27,7 +28,7 @@ export const SnippetsContext = createContext<Context>({
});
interface Props {
children: JSX.Element | JSX.Element[];
children: ReactNode;
}
export const SnippetsContextProvider = (props: Props): JSX.Element => {
@@ -153,7 +154,7 @@ export const SnippetsContextProvider = (props: Props): JSX.Element => {
.catch(err => console.log(err));
};
const context = {
const context: Context = {
snippets,
searchResults,
currentSnippet,

View File

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

View File

@@ -1,6 +1,6 @@
import { TagCount, NewSnippet, Snippet, SearchQuery } from '.';
export interface Context {
export interface SnippetsContext {
snippets: Snippet[];
searchResults: Snippet[];
currentSnippet: Snippet | null;
@@ -15,3 +15,8 @@ export interface Context {
countTags: () => 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 {
name: 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 './findLanguage';
export * from './searchParser';
export * from './ProtectedRoute';