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 { 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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
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 { 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,
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
export * from './SnippetsContext';
|
export * from './SnippetsContext';
|
||||||
|
export * from './AuthContext';
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export interface Route {
|
export interface Route {
|
||||||
name: string;
|
name: string;
|
||||||
dest: 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 './badgeColor';
|
||||||
export * from './findLanguage';
|
export * from './findLanguage';
|
||||||
export * from './searchParser';
|
export * from './searchParser';
|
||||||
|
export * from './ProtectedRoute';
|
||||||
|
|||||||
Reference in New Issue
Block a user