Added auth form. Added login and logout actions

This commit is contained in:
Paweł Malak 2021-11-10 13:53:28 +01:00
parent f5ed85427e
commit e4690d5d9c
11 changed files with 199 additions and 31 deletions

View file

@ -131,7 +131,7 @@ export const AppForm = ({ app, modalHandler }: Props): JSX.Element => {
onChange={(e) => inputChangeHandler(e)}
/>
<span>
Use icon name from MDI.
Use icon name from MDI or pass a valid URL.
<a href="https://materialdesignicons.com/" target="blank">
{' '}
Click here for reference

View file

@ -205,7 +205,7 @@ export const BookmarksForm = ({
onChange={(e) => inputChangeHandler(e)}
/>
<span>
Use icon name from MDI.
Use icon name from MDI or pass a valid URL.
<a href="https://materialdesignicons.com/" target="blank">
{' '}
Click here for reference

View file

@ -1,8 +1,14 @@
.AppVersion {
.text {
color: var(--color-primary);
margin-bottom: 15px;
}
.AppVersion a {
.text a,
.text span {
color: var(--color-accent);
}
}
.separator {
margin: 30px 0;
border: 1px solid var(--color-primary);
}

View file

@ -1,33 +1,100 @@
import { Fragment } from 'react';
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 classes from './AppDetails.module.css';
import { Button } from '../../UI';
// Utils
import { checkVersion } from '../../../utility';
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>
<p className={classes.AppVersion}>
<a
href="https://github.com/pawelmalak/flame"
target="_blank"
rel="noreferrer"
>
Flame
</a>{' '}
version {process.env.REACT_APP_VERSION}
</p>
<p className={classes.AppVersion}>
See changelog{' '}
<a
href="https://github.com/pawelmalak/flame/blob/master/CHANGELOG.md"
target="_blank"
rel="noreferrer"
>
here
</a>
</p>
<Button click={() => checkVersion(true)}>Check for updates</Button>
<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>
)}
<hr className={classes.separator} />
<div>
<SettingsHeadline text="App version" />
<p className={classes.text}>
<a
href="https://github.com/pawelmalak/flame"
target="_blank"
rel="noreferrer"
>
Flame
</a>{' '}
version {process.env.REACT_APP_VERSION}
</p>
<p className={classes.text}>
See changelog{' '}
<a
href="https://github.com/pawelmalak/flame/blob/master/CHANGELOG.md"
target="_blank"
rel="noreferrer"
>
here
</a>
</p>
<Button click={() => checkVersion(true)}>Check for updates</Button>
</div>
</Fragment>
);
};

View file

@ -0,0 +1,40 @@
import { Dispatch } from 'redux';
import { ApiResponse } from '../../interfaces';
import { ActionType } from '../action-types';
import { LoginAction, LogoutAction } from '../actions/auth';
import axios, { AxiosError } from 'axios';
export const login =
(password: string) => async (dispatch: Dispatch<LoginAction>) => {
try {
const res = await axios.post<ApiResponse<{ token: string }>>(
'/api/auth',
{ password }
);
localStorage.setItem('token', res.data.data.token);
dispatch({
type: ActionType.login,
payload: res.data.data.token,
});
} catch (err) {
const apiError = err as AxiosError;
dispatch<any>({
type: ActionType.createNotification,
payload: {
title: 'Error',
message: apiError.response?.data.error,
},
});
}
};
export const logout = () => (dispatch: Dispatch<LogoutAction>) => {
localStorage.removeItem('token');
dispatch({
type: ActionType.logout,
});
};

View file

@ -3,3 +3,4 @@ export * from './config';
export * from './notification';
export * from './app';
export * from './bookmark';
export * from './auth';

View file

@ -37,4 +37,7 @@ export enum ActionType {
addBookmark = 'ADD_BOOKMARK',
deleteBookmark = 'DELETE_BOOKMARK',
updateBookmark = 'UPDATE_BOOKMARK',
// AUTH
login = 'LOGIN',
logout = 'LOGOUT',
}

View file

@ -0,0 +1,10 @@
import { ActionType } from '../action-types';
export interface LoginAction {
type: ActionType.login;
payload: string;
}
export interface LogoutAction {
type: ActionType.logout;
}

View file

@ -1,3 +1,5 @@
import { App } from '../../interfaces';
import { SetThemeAction } from './theme';
import {
@ -24,8 +26,6 @@ import {
SortAppsAction,
} from './app';
import { App } from '../../interfaces';
import {
GetCategoriesAction,
AddCategoryAction,
@ -39,6 +39,8 @@ import {
UpdateBookmarkAction,
} from './bookmark';
import { LoginAction, LogoutAction } from './auth';
export type Action =
// Theme
| SetThemeAction
@ -71,4 +73,7 @@ export type Action =
// Bookmarks
| AddBookmarkAction
| DeleteBookmarkAction
| UpdateBookmarkAction;
| UpdateBookmarkAction
// Auth
| LoginAction
| LogoutAction;

View file

@ -0,0 +1,34 @@
import { Action } from '../actions';
import { ActionType } from '../action-types';
interface AuthState {
isAuthenticated: boolean;
token: string | null;
}
const initialState: AuthState = {
isAuthenticated: false,
token: null,
};
export const authReducer = (
state: AuthState = initialState,
action: Action
): AuthState => {
switch (action.type) {
case ActionType.login:
return {
...state,
token: action.payload,
isAuthenticated: true,
};
case ActionType.logout:
return {
...state,
token: null,
isAuthenticated: false,
};
default:
return state;
}
};

View file

@ -5,6 +5,7 @@ import { configReducer } from './config';
import { notificationReducer } from './notification';
import { appsReducer } from './app';
import { bookmarksReducer } from './bookmark';
import { authReducer } from './auth';
export const reducers = combineReducers({
theme: themeReducer,
@ -12,6 +13,7 @@ export const reducers = combineReducers({
notification: notificationReducer,
apps: appsReducer,
bookmarks: bookmarksReducer,
auth: authReducer,
});
export type State = ReturnType<typeof reducers>;