diff --git a/client/src/components/Apps/AppForm/AppForm.tsx b/client/src/components/Apps/AppForm/AppForm.tsx index 5c3125f..bdfa170 100644 --- a/client/src/components/Apps/AppForm/AppForm.tsx +++ b/client/src/components/Apps/AppForm/AppForm.tsx @@ -131,7 +131,7 @@ export const AppForm = ({ app, modalHandler }: Props): JSX.Element => { onChange={(e) => inputChangeHandler(e)} /> - Use icon name from MDI. + Use icon name from MDI or pass a valid URL. {' '} Click here for reference diff --git a/client/src/components/Bookmarks/Form/BookmarksForm.tsx b/client/src/components/Bookmarks/Form/BookmarksForm.tsx index 8a19fbd..e5f790b 100644 --- a/client/src/components/Bookmarks/Form/BookmarksForm.tsx +++ b/client/src/components/Bookmarks/Form/BookmarksForm.tsx @@ -205,7 +205,7 @@ export const BookmarksForm = ({ onChange={(e) => inputChangeHandler(e)} /> - Use icon name from MDI. + Use icon name from MDI or pass a valid URL. {' '} Click here for reference diff --git a/client/src/components/Settings/AppDetails/AppDetails.module.css b/client/src/components/Settings/AppDetails/AppDetails.module.css index 8f5fae3..6a7b939 100644 --- a/client/src/components/Settings/AppDetails/AppDetails.module.css +++ b/client/src/components/Settings/AppDetails/AppDetails.module.css @@ -1,8 +1,14 @@ -.AppVersion { +.text { color: var(--color-primary); margin-bottom: 15px; } -.AppVersion a { +.text a, +.text span { color: var(--color-accent); -} \ No newline at end of file +} + +.separator { + margin: 30px 0; + border: 1px solid var(--color-primary); +} diff --git a/client/src/components/Settings/AppDetails/AppDetails.tsx b/client/src/components/Settings/AppDetails/AppDetails.tsx index 42257ed..420b95c 100644 --- a/client/src/components/Settings/AppDetails/AppDetails.tsx +++ b/client/src/components/Settings/AppDetails/AppDetails.tsx @@ -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 ( -

- - Flame - {' '} - version {process.env.REACT_APP_VERSION} -

-

- See changelog{' '} - - here - -

- + + {!isAuthenticated ? ( +
+ + + setPassword(e.target.value)} + /> + + See + + {` project wiki `} + + to read more about authentication + + + + +
+ ) : ( +
+

+ You are logged in. Your session will expire @@@@ +

+ +
+ )} + +
+ +
+ +

+ + Flame + {' '} + version {process.env.REACT_APP_VERSION} +

+ +

+ See changelog{' '} + + here + +

+ + +
); }; diff --git a/client/src/store/action-creators/auth.ts b/client/src/store/action-creators/auth.ts new file mode 100644 index 0000000..6f2a775 --- /dev/null +++ b/client/src/store/action-creators/auth.ts @@ -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) => { + try { + const res = await axios.post>( + '/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({ + type: ActionType.createNotification, + payload: { + title: 'Error', + message: apiError.response?.data.error, + }, + }); + } + }; + +export const logout = () => (dispatch: Dispatch) => { + localStorage.removeItem('token'); + + dispatch({ + type: ActionType.logout, + }); +}; diff --git a/client/src/store/action-creators/index.ts b/client/src/store/action-creators/index.ts index ae038b0..12e4f0e 100644 --- a/client/src/store/action-creators/index.ts +++ b/client/src/store/action-creators/index.ts @@ -3,3 +3,4 @@ export * from './config'; export * from './notification'; export * from './app'; export * from './bookmark'; +export * from './auth'; diff --git a/client/src/store/action-types/index.ts b/client/src/store/action-types/index.ts index ab7d5ef..b68f8cf 100644 --- a/client/src/store/action-types/index.ts +++ b/client/src/store/action-types/index.ts @@ -37,4 +37,7 @@ export enum ActionType { addBookmark = 'ADD_BOOKMARK', deleteBookmark = 'DELETE_BOOKMARK', updateBookmark = 'UPDATE_BOOKMARK', + // AUTH + login = 'LOGIN', + logout = 'LOGOUT', } diff --git a/client/src/store/actions/auth.ts b/client/src/store/actions/auth.ts new file mode 100644 index 0000000..04a011f --- /dev/null +++ b/client/src/store/actions/auth.ts @@ -0,0 +1,10 @@ +import { ActionType } from '../action-types'; + +export interface LoginAction { + type: ActionType.login; + payload: string; +} + +export interface LogoutAction { + type: ActionType.logout; +} diff --git a/client/src/store/actions/index.ts b/client/src/store/actions/index.ts index af999a6..48c4e90 100644 --- a/client/src/store/actions/index.ts +++ b/client/src/store/actions/index.ts @@ -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; diff --git a/client/src/store/reducers/auth.ts b/client/src/store/reducers/auth.ts new file mode 100644 index 0000000..110bd8c --- /dev/null +++ b/client/src/store/reducers/auth.ts @@ -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; + } +}; diff --git a/client/src/store/reducers/index.ts b/client/src/store/reducers/index.ts index 1eed183..70571d5 100644 --- a/client/src/store/reducers/index.ts +++ b/client/src/store/reducers/index.ts @@ -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;