mirror of
https://github.com/pawelmalak/snippet-box.git
synced 2025-12-21 13:23:05 +01:00
Added AuthContext. Protected routes and functionality based on isAuthenticated property
This commit is contained in:
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
29
client/src/store/AuthContext.tsx
Normal file
29
client/src/store/AuthContext.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@@ -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,
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from './SnippetsContext';
|
||||
export * from './AuthContext';
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export interface Route {
|
||||
name: string;
|
||||
dest: string;
|
||||
isPublic: boolean;
|
||||
}
|
||||
|
||||
15
client/src/utils/ProtectedRoute.tsx
Normal file
15
client/src/utils/ProtectedRoute.tsx
Normal 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' />;
|
||||
}
|
||||
};
|
||||
@@ -2,3 +2,4 @@ export * from './dateParser';
|
||||
export * from './badgeColor';
|
||||
export * from './findLanguage';
|
||||
export * from './searchParser';
|
||||
export * from './ProtectedRoute';
|
||||
|
||||
Reference in New Issue
Block a user