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:
parent
f5ed85427e
commit
e4690d5d9c
11 changed files with 199 additions and 31 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
40
client/src/store/action-creators/auth.ts
Normal file
40
client/src/store/action-creators/auth.ts
Normal 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,
|
||||||
|
});
|
||||||
|
};
|
|
@ -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';
|
||||||
|
|
|
@ -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',
|
||||||
}
|
}
|
||||||
|
|
10
client/src/store/actions/auth.ts
Normal file
10
client/src/store/actions/auth.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { ActionType } from '../action-types';
|
||||||
|
|
||||||
|
export interface LoginAction {
|
||||||
|
type: ActionType.login;
|
||||||
|
payload: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LogoutAction {
|
||||||
|
type: ActionType.logout;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
34
client/src/store/reducers/auth.ts
Normal file
34
client/src/store/reducers/auth.ts
Normal 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;
|
||||||
|
}
|
||||||
|
};
|
|
@ -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>;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue