1
0
Fork 0
mirror of https://github.com/pawelmalak/flame.git synced 2025-07-19 11:39:36 +02:00

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)} onChange={(e) => inputChangeHandler(e)}
/> />
<span> <span>
Use icon name from MDI. Use icon name from MDI or pass a valid URL.
<a href="https://materialdesignicons.com/" target="blank"> <a href="https://materialdesignicons.com/" target="blank">
{' '} {' '}
Click here for reference Click here for reference

View file

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

View file

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

View file

@ -1,13 +1,77 @@
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 classes from './AppDetails.module.css';
import { Button } from '../../UI';
// Utils
import { checkVersion } from '../../../utility'; import { checkVersion } from '../../../utility';
export const AppDetails = (): JSX.Element => { 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 ( return (
<Fragment> <Fragment>
<p className={classes.AppVersion}> <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 <a
href="https://github.com/pawelmalak/flame" href="https://github.com/pawelmalak/flame"
target="_blank" target="_blank"
@ -17,7 +81,8 @@ export const AppDetails = (): JSX.Element => {
</a>{' '} </a>{' '}
version {process.env.REACT_APP_VERSION} version {process.env.REACT_APP_VERSION}
</p> </p>
<p className={classes.AppVersion}>
<p className={classes.text}>
See changelog{' '} See changelog{' '}
<a <a
href="https://github.com/pawelmalak/flame/blob/master/CHANGELOG.md" href="https://github.com/pawelmalak/flame/blob/master/CHANGELOG.md"
@ -27,7 +92,9 @@ export const AppDetails = (): JSX.Element => {
here here
</a> </a>
</p> </p>
<Button click={() => checkVersion(true)}>Check for updates</Button> <Button click={() => checkVersion(true)}>Check for updates</Button>
</div>
</Fragment> </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 './notification';
export * from './app'; export * from './app';
export * from './bookmark'; export * from './bookmark';
export * from './auth';

View file

@ -37,4 +37,7 @@ export enum ActionType {
addBookmark = 'ADD_BOOKMARK', addBookmark = 'ADD_BOOKMARK',
deleteBookmark = 'DELETE_BOOKMARK', deleteBookmark = 'DELETE_BOOKMARK',
updateBookmark = 'UPDATE_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 { SetThemeAction } from './theme';
import { import {
@ -24,8 +26,6 @@ import {
SortAppsAction, SortAppsAction,
} from './app'; } from './app';
import { App } from '../../interfaces';
import { import {
GetCategoriesAction, GetCategoriesAction,
AddCategoryAction, AddCategoryAction,
@ -39,6 +39,8 @@ import {
UpdateBookmarkAction, UpdateBookmarkAction,
} from './bookmark'; } from './bookmark';
import { LoginAction, LogoutAction } from './auth';
export type Action = export type Action =
// Theme // Theme
| SetThemeAction | SetThemeAction
@ -71,4 +73,7 @@ export type Action =
// Bookmarks // Bookmarks
| AddBookmarkAction | AddBookmarkAction
| DeleteBookmarkAction | 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 { notificationReducer } from './notification';
import { appsReducer } from './app'; import { appsReducer } from './app';
import { bookmarksReducer } from './bookmark'; import { bookmarksReducer } from './bookmark';
import { authReducer } from './auth';
export const reducers = combineReducers({ export const reducers = combineReducers({
theme: themeReducer, theme: themeReducer,
@ -12,6 +13,7 @@ export const reducers = combineReducers({
notification: notificationReducer, notification: notificationReducer,
apps: appsReducer, apps: appsReducer,
bookmarks: bookmarksReducer, bookmarks: bookmarksReducer,
auth: authReducer,
}); });
export type State = ReturnType<typeof reducers>; export type State = ReturnType<typeof reducers>;