mirror of
https://github.com/pawelmalak/flame.git
synced 2025-07-27 23:09:35 +02:00
Moved auth form. Added auto login and logout functionality
This commit is contained in:
parent
1571981252
commit
d1c61bb393
18 changed files with 311 additions and 98 deletions
13
client/src/components/Routing/ProtectedRoute.tsx
Normal file
13
client/src/components/Routing/ProtectedRoute.tsx
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { useSelector } from 'react-redux';
|
||||
import { Redirect, Route, RouteProps } from 'react-router';
|
||||
import { State } from '../../store/reducers';
|
||||
|
||||
export const ProtectedRoute = ({ ...rest }: RouteProps) => {
|
||||
const { isAuthenticated } = useSelector((state: State) => state.auth);
|
||||
|
||||
if (isAuthenticated) {
|
||||
return <Route {...rest} />;
|
||||
} else {
|
||||
return <Redirect to="/settings/app" />;
|
||||
}
|
||||
};
|
|
@ -1,71 +1,14 @@
|
|||
import { FormEvent, Fragment, useState } from 'react';
|
||||
|
||||
// Redux
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { actionCreators } from '../../../store';
|
||||
import { State } from '../../../store/reducers';
|
||||
|
||||
// UI
|
||||
import { Button, InputGroup, SettingsHeadline } from '../../UI';
|
||||
|
||||
// CSS
|
||||
import { Fragment } from 'react';
|
||||
import { Button, SettingsHeadline } from '../../UI';
|
||||
import classes from './AppDetails.module.css';
|
||||
|
||||
// Utils
|
||||
import { checkVersion } from '../../../utility';
|
||||
import { AuthForm } from './AuthForm/AuthForm';
|
||||
|
||||
export const AppDetails = (): JSX.Element => {
|
||||
const { isAuthenticated } = useSelector((state: State) => state.auth);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { login, logout } = bindActionCreators(actionCreators, dispatch);
|
||||
|
||||
const [password, setPassword] = useState('');
|
||||
|
||||
const formHandler = (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
login(password);
|
||||
setPassword('');
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<SettingsHeadline text="Authentication" />
|
||||
{!isAuthenticated ? (
|
||||
<form onSubmit={formHandler}>
|
||||
<InputGroup>
|
||||
<label htmlFor="password">Password</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
placeholder="••••••"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
<span>
|
||||
See
|
||||
<a
|
||||
href="https://github.com/pawelmalak/flame/wiki/Authentication"
|
||||
target="blank"
|
||||
>
|
||||
{` project wiki `}
|
||||
</a>
|
||||
to read more about authentication
|
||||
</span>
|
||||
</InputGroup>
|
||||
|
||||
<Button>Login</Button>
|
||||
</form>
|
||||
) : (
|
||||
<div>
|
||||
<p className={classes.text}>
|
||||
You are logged in. Your session will expire <span>@@@@</span>
|
||||
</p>
|
||||
<Button click={logout}>Logout</Button>
|
||||
</div>
|
||||
)}
|
||||
<AuthForm />
|
||||
|
||||
<hr className={classes.separator} />
|
||||
|
||||
|
|
104
client/src/components/Settings/AppDetails/AuthForm/AuthForm.tsx
Normal file
104
client/src/components/Settings/AppDetails/AuthForm/AuthForm.tsx
Normal file
|
@ -0,0 +1,104 @@
|
|||
import { FormEvent, Fragment, useEffect, useState } from 'react';
|
||||
|
||||
// Redux
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { actionCreators } from '../../../../store';
|
||||
import { State } from '../../../../store/reducers';
|
||||
import { decodeToken, parseTokenExpire } from '../../../../utility';
|
||||
|
||||
// Other
|
||||
import { InputGroup, Button } from '../../../UI';
|
||||
import classes from '../AppDetails.module.css';
|
||||
|
||||
export const AuthForm = (): JSX.Element => {
|
||||
const { isAuthenticated, token } = useSelector((state: State) => state.auth);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { login, logout } = bindActionCreators(actionCreators, dispatch);
|
||||
|
||||
const [tokenExpires, setTokenExpires] = useState('');
|
||||
const [formData, setFormData] = useState({
|
||||
password: '',
|
||||
duration: '14d',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (token) {
|
||||
const decoded = decodeToken(token);
|
||||
const expiresIn = parseTokenExpire(decoded.exp);
|
||||
setTokenExpires(expiresIn);
|
||||
}
|
||||
}, [token]);
|
||||
|
||||
const formHandler = (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
login(formData);
|
||||
setFormData({
|
||||
password: '',
|
||||
duration: '14d',
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{!isAuthenticated ? (
|
||||
<form onSubmit={formHandler}>
|
||||
<InputGroup>
|
||||
<label htmlFor="password">Password</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
placeholder="••••••"
|
||||
value={formData.password}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, password: e.target.value })
|
||||
}
|
||||
/>
|
||||
<span>
|
||||
See
|
||||
<a
|
||||
href="https://github.com/pawelmalak/flame/wiki/Authentication"
|
||||
target="blank"
|
||||
>
|
||||
{` project wiki `}
|
||||
</a>
|
||||
to read more about authentication
|
||||
</span>
|
||||
</InputGroup>
|
||||
|
||||
<InputGroup>
|
||||
<label htmlFor="duration">Session duration</label>
|
||||
<select
|
||||
id="duration"
|
||||
name="duration"
|
||||
value={formData.duration}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, duration: e.target.value })
|
||||
}
|
||||
>
|
||||
<option value="5s">dev: 5 seconds</option>
|
||||
<option value="10s">dev: 10 seconds</option>
|
||||
<option value="1h">1 hour</option>
|
||||
<option value="1d">1 day</option>
|
||||
<option value="14d">2 weeks</option>
|
||||
<option value="30d">1 month</option>
|
||||
<option value="1y">1 year</option>
|
||||
</select>
|
||||
</InputGroup>
|
||||
|
||||
<Button>Login</Button>
|
||||
</form>
|
||||
) : (
|
||||
<div>
|
||||
<p className={classes.text}>
|
||||
You are logged in. Your session will expire{' '}
|
||||
<span>{tokenExpires}</span>
|
||||
</p>
|
||||
<Button click={logout}>Logout</Button>
|
||||
</div>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
|
@ -1,5 +1,9 @@
|
|||
import { NavLink, Link, Switch, Route } from 'react-router-dom';
|
||||
|
||||
// Redux
|
||||
import { useSelector } from 'react-redux';
|
||||
import { State } from '../../store/reducers';
|
||||
|
||||
// Typescript
|
||||
import { Route as SettingsRoute } from '../../interfaces';
|
||||
|
||||
|
@ -14,6 +18,7 @@ import { AppDetails } from './AppDetails/AppDetails';
|
|||
import { StyleSettings } from './StyleSettings/StyleSettings';
|
||||
import { SearchSettings } from './SearchSettings/SearchSettings';
|
||||
import { DockerSettings } from './DockerSettings/DockerSettings';
|
||||
import { ProtectedRoute } from '../Routing/ProtectedRoute';
|
||||
|
||||
// UI
|
||||
import { Container, Headline } from '../UI';
|
||||
|
@ -22,13 +27,17 @@ import { Container, Headline } from '../UI';
|
|||
import { routes } from './settings.json';
|
||||
|
||||
export const Settings = (): JSX.Element => {
|
||||
const { isAuthenticated } = useSelector((state: State) => state.auth);
|
||||
|
||||
const tabs = isAuthenticated ? routes : routes.filter((r) => !r.authRequired);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Headline title="Settings" subtitle={<Link to="/">Go back</Link>} />
|
||||
<div className={classes.Settings}>
|
||||
{/* NAVIGATION MENU */}
|
||||
<nav className={classes.SettingsNav}>
|
||||
{routes.map(({ name, dest }: SettingsRoute, idx) => (
|
||||
{tabs.map(({ name, dest }: SettingsRoute, idx) => (
|
||||
<NavLink
|
||||
className={classes.SettingsNavLink}
|
||||
activeClassName={classes.SettingsNavLinkActive}
|
||||
|
@ -45,11 +54,20 @@ export const Settings = (): JSX.Element => {
|
|||
<section className={classes.SettingsContent}>
|
||||
<Switch>
|
||||
<Route exact path="/settings" component={Themer} />
|
||||
<Route path="/settings/weather" component={WeatherSettings} />
|
||||
<Route path="/settings/search" component={SearchSettings} />
|
||||
<Route path="/settings/interface" component={UISettings} />
|
||||
<Route path="/settings/docker" component={DockerSettings} />
|
||||
<Route path="/settings/css" component={StyleSettings} />
|
||||
<ProtectedRoute
|
||||
path="/settings/weather"
|
||||
component={WeatherSettings}
|
||||
/>
|
||||
<ProtectedRoute
|
||||
path="/settings/search"
|
||||
component={SearchSettings}
|
||||
/>
|
||||
<ProtectedRoute path="/settings/interface" component={UISettings} />
|
||||
<ProtectedRoute
|
||||
path="/settings/docker"
|
||||
component={DockerSettings}
|
||||
/>
|
||||
<ProtectedRoute path="/settings/css" component={StyleSettings} />
|
||||
<Route path="/settings/app" component={AppDetails} />
|
||||
</Switch>
|
||||
</section>
|
||||
|
|
|
@ -2,31 +2,38 @@
|
|||
"routes": [
|
||||
{
|
||||
"name": "Theme",
|
||||
"dest": "/settings"
|
||||
"dest": "/settings",
|
||||
"authRequired": false
|
||||
},
|
||||
{
|
||||
"name": "Weather",
|
||||
"dest": "/settings/weather"
|
||||
"dest": "/settings/weather",
|
||||
"authRequired": true
|
||||
},
|
||||
{
|
||||
"name": "Search",
|
||||
"dest": "/settings/search"
|
||||
"dest": "/settings/search",
|
||||
"authRequired": true
|
||||
},
|
||||
{
|
||||
"name": "Interface",
|
||||
"dest": "/settings/interface"
|
||||
"dest": "/settings/interface",
|
||||
"authRequired": true
|
||||
},
|
||||
{
|
||||
"name": "Docker",
|
||||
"dest": "/settings/docker"
|
||||
"dest": "/settings/docker",
|
||||
"authRequired": true
|
||||
},
|
||||
{
|
||||
"name": "CSS",
|
||||
"dest": "/settings/css"
|
||||
"dest": "/settings/css",
|
||||
"authRequired": true
|
||||
},
|
||||
{
|
||||
"name": "App",
|
||||
"dest": "/settings/app"
|
||||
"dest": "/settings/app",
|
||||
"authRequired": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue