diff --git a/client/src/store/action-creators/config.ts b/client/src/store/action-creators/config.ts new file mode 100644 index 0000000..6bbde52 --- /dev/null +++ b/client/src/store/action-creators/config.ts @@ -0,0 +1,141 @@ +import { Dispatch } from 'redux'; +import { + AddQueryAction, + DeleteQueryAction, + FetchQueriesAction, + GetConfigAction, + UpdateConfigAction, + UpdateQueryAction, +} from '../actions/config'; +import axios from 'axios'; +import { + ApiResponse, + Config, + OtherSettingsForm, + Query, + SearchForm, + WeatherForm, +} from '../../interfaces'; +import { ActionType } from '../action-types'; +import { storeUIConfig } from '../../utility'; +import { createNotification } from '.'; + +export const getConfig = () => async (dispatch: Dispatch) => { + try { + const res = await axios.get>('/api/config'); + + dispatch({ + type: ActionType.getConfig, + payload: res.data.data, + }); + + // Set custom page title if set + document.title = res.data.data.customTitle; + + // Store settings for priority UI elements + const keys: (keyof Config)[] = [ + 'useAmericanDate', + 'greetingsSchema', + 'daySchema', + 'monthSchema', + ]; + for (let key of keys) { + storeUIConfig(key, res.data.data); + } + } catch (err) { + console.log(err); + } +}; + +export const updateConfig = + (formData: WeatherForm | OtherSettingsForm | SearchForm) => + async (dispatch: Dispatch) => { + try { + const res = await axios.put>('/api/config', formData); + + createNotification({ + title: 'Success', + message: 'Settings updated', + }); + + dispatch({ + type: ActionType.updateConfig, + payload: res.data.data, + }); + + // Store settings for priority UI elements + const keys: (keyof Config)[] = [ + 'useAmericanDate', + 'greetingsSchema', + 'daySchema', + 'monthSchema', + ]; + for (let key of keys) { + storeUIConfig(key, res.data.data); + } + } catch (err) { + console.log(err); + } + }; + +export const fetchQueries = + () => async (dispatch: Dispatch) => { + try { + const res = await axios.get>('/api/queries'); + + dispatch({ + type: ActionType.fetchQueries, + payload: res.data.data, + }); + } catch (err) { + console.log(err); + } + }; + +export const addQuery = + (query: Query) => async (dispatch: Dispatch) => { + try { + const res = await axios.post>('/api/queries', query); + + dispatch({ + type: ActionType.addQuery, + payload: res.data.data, + }); + } catch (err) { + console.log(err); + } + }; + +export const deleteQuery = + (prefix: string) => async (dispatch: Dispatch) => { + try { + const res = await axios.delete>( + `/api/queries/${prefix}` + ); + + dispatch({ + type: ActionType.deleteQuery, + payload: res.data.data, + }); + } catch (err) { + console.log(err); + } + }; + +export const updateQuery = + (query: Query, oldPrefix: string) => + async (dispatch: Dispatch) => { + try { + const res = await axios.put>( + `/api/queries/${oldPrefix}`, + query + ); + + dispatch({ + type: ActionType.updateQuery, + payload: res.data.data, + }); + } catch (err) { + console.log(err); + } + }; diff --git a/client/src/store/action-creators/index.ts b/client/src/store/action-creators/index.ts new file mode 100644 index 0000000..d7b5e45 --- /dev/null +++ b/client/src/store/action-creators/index.ts @@ -0,0 +1,3 @@ +export * from './theme'; +export * from './config'; +export * from './notification'; diff --git a/client/src/store/action-creators/notification.ts b/client/src/store/action-creators/notification.ts new file mode 100644 index 0000000..b46e27c --- /dev/null +++ b/client/src/store/action-creators/notification.ts @@ -0,0 +1,24 @@ +import { Dispatch } from 'redux'; +import { NewNotification } from '../../interfaces'; +import { ActionType } from '../action-types'; +import { + CreateNotificationAction, + ClearNotificationAction, +} from '../actions/notification'; + +export const createNotification = + (notification: NewNotification) => + (dispatch: Dispatch) => { + dispatch({ + type: ActionType.createNotification, + payload: notification, + }); + }; + +export const clearNotification = + (id: number) => (dispatch: Dispatch) => { + dispatch({ + type: ActionType.clearNotification, + payload: id, + }); + }; diff --git a/client/src/store/action-creators/theme.ts b/client/src/store/action-creators/theme.ts new file mode 100644 index 0000000..ba52d27 --- /dev/null +++ b/client/src/store/action-creators/theme.ts @@ -0,0 +1,26 @@ +import { Dispatch } from 'redux'; +import { SetThemeAction } from '../actions/theme'; +import { ActionType } from '../action-types'; +import { Theme } from '../../interfaces/Theme'; +import { themes } from '../../components/Themer/themes.json'; + +export const setTheme = + (name: string) => (dispatch: Dispatch) => { + const theme = themes.find((theme) => theme.name === name); + + if (theme) { + localStorage.setItem('theme', name); + loadTheme(theme); + + dispatch({ + type: ActionType.setTheme, + payload: theme, + }); + } + }; + +export const loadTheme = (theme: Theme): void => { + for (const [key, value] of Object.entries(theme.colors)) { + document.body.style.setProperty(`--color-${key}`, value); + } +}; diff --git a/client/src/store/action-types/index.ts b/client/src/store/action-types/index.ts new file mode 100644 index 0000000..eb32118 --- /dev/null +++ b/client/src/store/action-types/index.ts @@ -0,0 +1,14 @@ +export enum ActionType { + // THEME + setTheme = 'SET_THEME', + // CONFIG + getConfig = 'GET_CONFIG', + updateConfig = 'UPDATE_CONFIG', + addQuery = 'ADD_QUERY', + deleteQuery = 'DELETE_QUERY', + fetchQueries = 'FETCH_QUERIES', + updateQuery = 'UPDATE_QUERY', + // NOTIFICATIONS + createNotification = 'CREATE_NOTIFICATION', + clearNotification = 'CLEAR_NOTIFICATION', +} diff --git a/client/src/store/actions/actionTypes.ts b/client/src/store/actions/actionTypes.ts deleted file mode 100644 index c670b2f..0000000 --- a/client/src/store/actions/actionTypes.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { - // Theme - SetThemeAction, - // Apps - GetAppsAction, - PinAppAction, - AddAppAction, - DeleteAppAction, - UpdateAppAction, - ReorderAppsAction, - SortAppsAction, - // Categories - GetCategoriesAction, - AddCategoryAction, - PinCategoryAction, - DeleteCategoryAction, - UpdateCategoryAction, - SortCategoriesAction, - ReorderCategoriesAction, - // Bookmarks - AddBookmarkAction, - DeleteBookmarkAction, - UpdateBookmarkAction, - // Notifications - CreateNotificationAction, - ClearNotificationAction, - // Config - GetConfigAction, - UpdateConfigAction, -} from './'; -import { - AddQueryAction, - DeleteQueryAction, - FetchQueriesAction, - UpdateQueryAction, -} from './config'; - -export enum ActionTypes { - // Theme - setTheme = 'SET_THEME', - // Apps - getApps = 'GET_APPS', - getAppsSuccess = 'GET_APPS_SUCCESS', - getAppsError = 'GET_APPS_ERROR', - pinApp = 'PIN_APP', - addApp = 'ADD_APP', - addAppSuccess = 'ADD_APP_SUCCESS', - deleteApp = 'DELETE_APP', - updateApp = 'UPDATE_APP', - reorderApps = 'REORDER_APPS', - sortApps = 'SORT_APPS', - // Categories - getCategories = 'GET_CATEGORIES', - getCategoriesSuccess = 'GET_CATEGORIES_SUCCESS', - getCategoriesError = 'GET_CATEGORIES_ERROR', - addCategory = 'ADD_CATEGORY', - pinCategory = 'PIN_CATEGORY', - deleteCategory = 'DELETE_CATEGORY', - updateCategory = 'UPDATE_CATEGORY', - sortCategories = 'SORT_CATEGORIES', - reorderCategories = 'REORDER_CATEGORIES', - // Bookmarks - addBookmark = 'ADD_BOOKMARK', - deleteBookmark = 'DELETE_BOOKMARK', - updateBookmark = 'UPDATE_BOOKMARK', - // Notifications - createNotification = 'CREATE_NOTIFICATION', - clearNotification = 'CLEAR_NOTIFICATION', - // Config - getConfig = 'GET_CONFIG', - updateConfig = 'UPDATE_CONFIG', - fetchQueries = 'FETCH_QUERIES', - addQuery = 'ADD_QUERY', - deleteQuery = 'DELETE_QUERY', - updateQuery = 'UPDATE_QUERY', -} - -export type Action = - // Theme - | SetThemeAction - // Apps - | GetAppsAction - | PinAppAction - | AddAppAction - | DeleteAppAction - | UpdateAppAction - | ReorderAppsAction - | SortAppsAction - // Categories - | GetCategoriesAction - | AddCategoryAction - | PinCategoryAction - | DeleteCategoryAction - | UpdateCategoryAction - | SortCategoriesAction - | ReorderCategoriesAction - // Bookmarks - | AddBookmarkAction - | DeleteBookmarkAction - | UpdateBookmarkAction - // Notifications - | CreateNotificationAction - | ClearNotificationAction - // Config - | GetConfigAction - | UpdateConfigAction - | FetchQueriesAction - | AddQueryAction - | DeleteQueryAction - | UpdateQueryAction; diff --git a/client/src/store/actions/app.ts b/client/src/store/actions/app.ts deleted file mode 100644 index b33a78b..0000000 --- a/client/src/store/actions/app.ts +++ /dev/null @@ -1,205 +0,0 @@ -import axios from 'axios'; -import { Dispatch } from 'redux'; -import { ActionTypes } from './actionTypes'; -import { App, ApiResponse, NewApp, Config } from '../../interfaces'; -import { CreateNotificationAction } from './notification'; - -export interface GetAppsAction { - type: - | ActionTypes.getApps - | ActionTypes.getAppsSuccess - | ActionTypes.getAppsError; - payload: T; -} - -export const getApps = () => async (dispatch: Dispatch) => { - dispatch>({ - type: ActionTypes.getApps, - payload: undefined, - }); - - try { - const res = await axios.get>('/api/apps'); - - dispatch>({ - type: ActionTypes.getAppsSuccess, - payload: res.data.data, - }); - } catch (err) { - console.log(err); - } -}; - -export interface PinAppAction { - type: ActionTypes.pinApp; - payload: App; -} - -export const pinApp = (app: App) => async (dispatch: Dispatch) => { - try { - const { id, isPinned, name } = app; - const res = await axios.put>(`/api/apps/${id}`, { - isPinned: !isPinned, - }); - - const status = isPinned - ? 'unpinned from Homescreen' - : 'pinned to Homescreen'; - - dispatch({ - type: ActionTypes.createNotification, - payload: { - title: 'Success', - message: `App ${name} ${status}`, - }, - }); - - dispatch({ - type: ActionTypes.pinApp, - payload: res.data.data, - }); - } catch (err) { - console.log(err); - } -}; - -export interface AddAppAction { - type: ActionTypes.addAppSuccess; - payload: App; -} - -export const addApp = - (formData: NewApp | FormData) => async (dispatch: Dispatch) => { - try { - const res = await axios.post>('/api/apps', formData); - - dispatch({ - type: ActionTypes.createNotification, - payload: { - title: 'Success', - message: `App added`, - }, - }); - - await dispatch({ - type: ActionTypes.addAppSuccess, - payload: res.data.data, - }); - - // Sort apps - dispatch(sortApps()); - } catch (err) { - console.log(err); - } - }; - -export interface DeleteAppAction { - type: ActionTypes.deleteApp; - payload: number; -} - -export const deleteApp = (id: number) => async (dispatch: Dispatch) => { - try { - await axios.delete>(`/api/apps/${id}`); - - dispatch({ - type: ActionTypes.createNotification, - payload: { - title: 'Success', - message: 'App deleted', - }, - }); - - dispatch({ - type: ActionTypes.deleteApp, - payload: id, - }); - } catch (err) { - console.log(err); - } -}; - -export interface UpdateAppAction { - type: ActionTypes.updateApp; - payload: App; -} - -export const updateApp = - (id: number, formData: NewApp | FormData) => async (dispatch: Dispatch) => { - try { - const res = await axios.put>( - `/api/apps/${id}`, - formData - ); - - dispatch({ - type: ActionTypes.createNotification, - payload: { - title: 'Success', - message: `App updated`, - }, - }); - - await dispatch({ - type: ActionTypes.updateApp, - payload: res.data.data, - }); - - // Sort apps - dispatch(sortApps()); - } catch (err) { - console.log(err); - } - }; - -export interface ReorderAppsAction { - type: ActionTypes.reorderApps; - payload: App[]; -} - -interface ReorderQuery { - apps: { - id: number; - orderId: number; - }[]; -} - -export const reorderApps = (apps: App[]) => async (dispatch: Dispatch) => { - try { - const updateQuery: ReorderQuery = { apps: [] }; - - apps.forEach((app, index) => - updateQuery.apps.push({ - id: app.id, - orderId: index + 1, - }) - ); - - await axios.put>('/api/apps/0/reorder', updateQuery); - - dispatch({ - type: ActionTypes.reorderApps, - payload: apps, - }); - } catch (err) { - console.log(err); - } -}; - -export interface SortAppsAction { - type: ActionTypes.sortApps; - payload: string; -} - -export const sortApps = () => async (dispatch: Dispatch) => { - try { - const res = await axios.get>('/api/config'); - - dispatch({ - type: ActionTypes.sortApps, - payload: res.data.data.useOrdering, - }); - } catch (err) { - console.log(err); - } -}; diff --git a/client/src/store/actions/bookmark.ts b/client/src/store/actions/bookmark.ts deleted file mode 100644 index 6d6fdf5..0000000 --- a/client/src/store/actions/bookmark.ts +++ /dev/null @@ -1,371 +0,0 @@ -import axios from 'axios'; -import { Dispatch } from 'redux'; -import { ActionTypes } from './actionTypes'; -import { - Category, - ApiResponse, - NewCategory, - Bookmark, - NewBookmark, - Config, -} from '../../interfaces'; -import { CreateNotificationAction } from './notification'; - -/** - * GET CATEGORIES - */ -export interface GetCategoriesAction { - type: - | ActionTypes.getCategories - | ActionTypes.getCategoriesSuccess - | ActionTypes.getCategoriesError; - payload: T; -} - -export const getCategories = () => async (dispatch: Dispatch) => { - dispatch>({ - type: ActionTypes.getCategories, - payload: undefined, - }); - - try { - const res = await axios.get>('/api/categories'); - - dispatch>({ - type: ActionTypes.getCategoriesSuccess, - payload: res.data.data, - }); - } catch (err) { - console.log(err); - } -}; - -/** - * ADD CATEGORY - */ -export interface AddCategoryAction { - type: ActionTypes.addCategory; - payload: Category; -} - -export const addCategory = - (formData: NewCategory) => async (dispatch: Dispatch) => { - try { - const res = await axios.post>( - '/api/categories', - formData - ); - - dispatch({ - type: ActionTypes.createNotification, - payload: { - title: 'Success', - message: `Category ${formData.name} created`, - }, - }); - - dispatch({ - type: ActionTypes.addCategory, - payload: res.data.data, - }); - - dispatch(sortCategories()); - } catch (err) { - console.log(err); - } - }; - -/** - * ADD BOOKMARK - */ -export interface AddBookmarkAction { - type: ActionTypes.addBookmark; - payload: Bookmark; -} - -export const addBookmark = - (formData: NewBookmark | FormData) => async (dispatch: Dispatch) => { - try { - const res = await axios.post>( - '/api/bookmarks', - formData - ); - - dispatch({ - type: ActionTypes.createNotification, - payload: { - title: 'Success', - message: `Bookmark created`, - }, - }); - - dispatch({ - type: ActionTypes.addBookmark, - payload: res.data.data, - }); - } catch (err) { - console.log(err); - } - }; - -/** - * PIN CATEGORY - */ -export interface PinCategoryAction { - type: ActionTypes.pinCategory; - payload: Category; -} - -export const pinCategory = - (category: Category) => async (dispatch: Dispatch) => { - try { - const { id, isPinned, name } = category; - const res = await axios.put>( - `/api/categories/${id}`, - { isPinned: !isPinned } - ); - - const status = isPinned - ? 'unpinned from Homescreen' - : 'pinned to Homescreen'; - - dispatch({ - type: ActionTypes.createNotification, - payload: { - title: 'Success', - message: `Category ${name} ${status}`, - }, - }); - - dispatch({ - type: ActionTypes.pinCategory, - payload: res.data.data, - }); - } catch (err) { - console.log(err); - } - }; - -/** - * DELETE CATEGORY - */ -export interface DeleteCategoryAction { - type: ActionTypes.deleteCategory; - payload: number; -} - -export const deleteCategory = (id: number) => async (dispatch: Dispatch) => { - try { - await axios.delete>(`/api/categories/${id}`); - - dispatch({ - type: ActionTypes.createNotification, - payload: { - title: 'Success', - message: `Category deleted`, - }, - }); - - dispatch({ - type: ActionTypes.deleteCategory, - payload: id, - }); - } catch (err) { - console.log(err); - } -}; - -/** - * UPDATE CATEGORY - */ -export interface UpdateCategoryAction { - type: ActionTypes.updateCategory; - payload: Category; -} - -export const updateCategory = - (id: number, formData: NewCategory) => async (dispatch: Dispatch) => { - try { - const res = await axios.put>( - `/api/categories/${id}`, - formData - ); - - dispatch({ - type: ActionTypes.createNotification, - payload: { - title: 'Success', - message: `Category ${formData.name} updated`, - }, - }); - - dispatch({ - type: ActionTypes.updateCategory, - payload: res.data.data, - }); - - dispatch(sortCategories()); - } catch (err) { - console.log(err); - } - }; - -/** - * DELETE BOOKMARK - */ -export interface DeleteBookmarkAction { - type: ActionTypes.deleteBookmark; - payload: { - bookmarkId: number; - categoryId: number; - }; -} - -export const deleteBookmark = - (bookmarkId: number, categoryId: number) => async (dispatch: Dispatch) => { - try { - await axios.delete>(`/api/bookmarks/${bookmarkId}`); - - dispatch({ - type: ActionTypes.createNotification, - payload: { - title: 'Success', - message: 'Bookmark deleted', - }, - }); - - dispatch({ - type: ActionTypes.deleteBookmark, - payload: { - bookmarkId, - categoryId, - }, - }); - } catch (err) { - console.log(err); - } - }; - -/** - * UPDATE BOOKMARK - */ -export interface UpdateBookmarkAction { - type: ActionTypes.updateBookmark; - payload: Bookmark; -} - -export const updateBookmark = - ( - bookmarkId: number, - formData: NewBookmark | FormData, - category: { - prev: number; - curr: number; - } - ) => - async (dispatch: Dispatch) => { - try { - const res = await axios.put>( - `/api/bookmarks/${bookmarkId}`, - formData - ); - - dispatch({ - type: ActionTypes.createNotification, - payload: { - title: 'Success', - message: `Bookmark updated`, - }, - }); - - // Check if category was changed - const categoryWasChanged = category.curr !== category.prev; - - if (categoryWasChanged) { - // Delete bookmark from old category - dispatch({ - type: ActionTypes.deleteBookmark, - payload: { - bookmarkId, - categoryId: category.prev, - }, - }); - - // Add bookmark to the new category - dispatch({ - type: ActionTypes.addBookmark, - payload: res.data.data, - }); - } else { - // Else update only name/url/icon - dispatch({ - type: ActionTypes.updateBookmark, - payload: res.data.data, - }); - } - } catch (err) { - console.log(err); - } - }; - -/** - * SORT CATEGORIES - */ -export interface SortCategoriesAction { - type: ActionTypes.sortCategories; - payload: string; -} - -export const sortCategories = () => async (dispatch: Dispatch) => { - try { - const res = await axios.get>('/api/config'); - - dispatch({ - type: ActionTypes.sortCategories, - payload: res.data.data.useOrdering, - }); - } catch (err) { - console.log(err); - } -}; - -/** - * REORDER CATEGORIES - */ -export interface ReorderCategoriesAction { - type: ActionTypes.reorderCategories; - payload: Category[]; -} - -interface ReorderQuery { - categories: { - id: number; - orderId: number; - }[]; -} - -export const reorderCategories = - (categories: Category[]) => async (dispatch: Dispatch) => { - try { - const updateQuery: ReorderQuery = { categories: [] }; - - categories.forEach((category, index) => - updateQuery.categories.push({ - id: category.id, - orderId: index + 1, - }) - ); - - await axios.put>( - '/api/categories/0/reorder', - updateQuery - ); - - dispatch({ - type: ActionTypes.reorderCategories, - payload: categories, - }); - } catch (err) { - console.log(err); - } - }; diff --git a/client/src/store/actions/config.ts b/client/src/store/actions/config.ts index 88e8ff3..42bf64d 100644 --- a/client/src/store/actions/config.ts +++ b/client/src/store/actions/config.ts @@ -1,157 +1,32 @@ -import axios from 'axios'; -import { Dispatch } from 'redux'; -import { ActionTypes } from './actionTypes'; -import { Config, ApiResponse, Query } from '../../interfaces'; -import { CreateNotificationAction } from './notification'; -import { storeUIConfig } from '../../utility'; +import { ActionType } from '../action-types'; +import { Config, Query } from '../../interfaces'; export interface GetConfigAction { - type: ActionTypes.getConfig; + type: ActionType.getConfig; payload: Config; } -export const getConfig = () => async (dispatch: Dispatch) => { - try { - const res = await axios.get>('/api/config'); - - dispatch({ - type: ActionTypes.getConfig, - payload: res.data.data, - }); - - // Set custom page title if set - document.title = res.data.data.customTitle; - - // Store settings for priority UI elements - const keys: (keyof Config)[] = [ - 'useAmericanDate', - 'greetingsSchema', - 'daySchema', - 'monthSchema', - ]; - for (let key of keys) { - storeUIConfig(key, res.data.data); - } - } catch (err) { - console.log(err); - } -}; - export interface UpdateConfigAction { - type: ActionTypes.updateConfig; + type: ActionType.updateConfig; payload: Config; } -export const updateConfig = (formData: any) => async (dispatch: Dispatch) => { - try { - const res = await axios.put>('/api/config', formData); - - dispatch({ - type: ActionTypes.createNotification, - payload: { - title: 'Success', - message: 'Settings updated', - }, - }); - - dispatch({ - type: ActionTypes.updateConfig, - payload: res.data.data, - }); - - // Store settings for priority UI elements - const keys: (keyof Config)[] = [ - 'useAmericanDate', - 'greetingsSchema', - 'daySchema', - 'monthSchema', - ]; - for (let key of keys) { - storeUIConfig(key, res.data.data); - } - } catch (err) { - console.log(err); - } -}; - export interface FetchQueriesAction { - type: ActionTypes.fetchQueries; + type: ActionType.fetchQueries; payload: Query[]; } -export const fetchQueries = - () => async (dispatch: Dispatch) => { - try { - const res = await axios.get>('/api/queries'); - - dispatch({ - type: ActionTypes.fetchQueries, - payload: res.data.data, - }); - } catch (err) { - console.log(err); - } - }; - export interface AddQueryAction { - type: ActionTypes.addQuery; + type: ActionType.addQuery; payload: Query; } -export const addQuery = - (query: Query) => async (dispatch: Dispatch) => { - try { - const res = await axios.post>('/api/queries', query); - - dispatch({ - type: ActionTypes.addQuery, - payload: res.data.data, - }); - } catch (err) { - console.log(err); - } - }; - export interface DeleteQueryAction { - type: ActionTypes.deleteQuery; + type: ActionType.deleteQuery; payload: Query[]; } -export const deleteQuery = - (prefix: string) => async (dispatch: Dispatch) => { - try { - const res = await axios.delete>( - `/api/queries/${prefix}` - ); - - dispatch({ - type: ActionTypes.deleteQuery, - payload: res.data.data, - }); - } catch (err) { - console.log(err); - } - }; - export interface UpdateQueryAction { - type: ActionTypes.updateQuery; + type: ActionType.updateQuery; payload: Query[]; } - -export const updateQuery = - (query: Query, oldPrefix: string) => - async (dispatch: Dispatch) => { - try { - const res = await axios.put>( - `/api/queries/${oldPrefix}`, - query - ); - - dispatch({ - type: ActionTypes.updateQuery, - payload: res.data.data, - }); - } catch (err) { - console.log(err); - } - }; diff --git a/client/src/store/actions/index.ts b/client/src/store/actions/index.ts index e516e54..d6bea13 100644 --- a/client/src/store/actions/index.ts +++ b/client/src/store/actions/index.ts @@ -1,6 +1,27 @@ -export * from './theme'; -export * from './app'; -export * from './actionTypes'; -export * from './bookmark'; -export * from './notification'; -export * from './config'; \ No newline at end of file +import { SetThemeAction } from './theme'; +import { + AddQueryAction, + DeleteQueryAction, + FetchQueriesAction, + GetConfigAction, + UpdateConfigAction, + UpdateQueryAction, +} from './config'; +import { + ClearNotificationAction, + CreateNotificationAction, +} from './notification'; + +export type Action = + // Theme + | SetThemeAction + // Config + | GetConfigAction + | UpdateConfigAction + | AddQueryAction + | DeleteQueryAction + | FetchQueriesAction + | UpdateQueryAction + // Notifications + | CreateNotificationAction + | ClearNotificationAction; diff --git a/client/src/store/actions/notification.ts b/client/src/store/actions/notification.ts index a4f17cf..e6e25ab 100644 --- a/client/src/store/actions/notification.ts +++ b/client/src/store/actions/notification.ts @@ -1,27 +1,12 @@ -import { Dispatch } from 'redux'; -import { ActionTypes } from '.'; +import { ActionType } from '../action-types'; import { NewNotification } from '../../interfaces'; export interface CreateNotificationAction { - type: ActionTypes.createNotification, - payload: NewNotification -} - -export const createNotification = (notification: NewNotification) => (dispatch: Dispatch) => { - dispatch({ - type: ActionTypes.createNotification, - payload: notification - }) + type: ActionType.createNotification; + payload: NewNotification; } export interface ClearNotificationAction { - type: ActionTypes.clearNotification, - payload: number + type: ActionType.clearNotification; + payload: number; } - -export const clearNotification = (id: number) => (dispatch: Dispatch) => { - dispatch({ - type: ActionTypes.clearNotification, - payload: id - }) -} \ No newline at end of file diff --git a/client/src/store/actions/theme.ts b/client/src/store/actions/theme.ts index 0134ead..036b1a3 100644 --- a/client/src/store/actions/theme.ts +++ b/client/src/store/actions/theme.ts @@ -1,29 +1,7 @@ -import { Dispatch } from 'redux'; -import { themes } from '../../components/Themer/themes.json'; -import { Theme } from '../../interfaces/Theme'; -import { ActionTypes } from './actionTypes'; +import { ActionType } from '../action-types'; +import { Theme } from '../../interfaces'; export interface SetThemeAction { - type: ActionTypes.setTheme, - payload: Theme + type: ActionType.setTheme; + payload: Theme; } - -export const setTheme = (themeName: string) => (dispatch: Dispatch) => { - const theme = themes.find((theme: Theme) => theme.name === themeName); - - if (theme) { - localStorage.setItem('theme', themeName); - loadTheme(theme); - - dispatch({ - type: ActionTypes.setTheme, - payload: theme - }) - } -} - -export const loadTheme = (theme: Theme): void => { - for (const [key, value] of Object.entries(theme.colors)) { - document.body.style.setProperty(`--color-${key}`, value); - } -} \ No newline at end of file diff --git a/client/src/store/index.ts b/client/src/store/index.ts new file mode 100644 index 0000000..c823dbc --- /dev/null +++ b/client/src/store/index.ts @@ -0,0 +1,2 @@ +export * from './store'; +export * as actionCreators from './action-creators'; diff --git a/client/src/store/reducers/app.ts b/client/src/store/reducers/app.ts deleted file mode 100644 index 9d4c359..0000000 --- a/client/src/store/reducers/app.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { ActionTypes, Action } from '../actions'; -import { App } from '../../interfaces/App'; -import { sortData } from '../../utility'; - -export interface State { - loading: boolean; - apps: App[]; - errors: string | undefined; -} - -const initialState: State = { - loading: true, - apps: [], - errors: undefined, -}; - -const getApps = (state: State, action: Action): State => { - return { - ...state, - loading: true, - errors: undefined, - }; -}; - -const getAppsSuccess = (state: State, action: Action): State => { - return { - ...state, - loading: false, - apps: action.payload, - }; -}; - -const getAppsError = (state: State, action: Action): State => { - return { - ...state, - loading: false, - errors: action.payload, - }; -}; - -const pinApp = (state: State, action: Action): State => { - const tmpApps = [...state.apps]; - const changedApp = tmpApps.find((app: App) => app.id === action.payload.id); - - if (changedApp) { - changedApp.isPinned = action.payload.isPinned; - } - - return { - ...state, - apps: tmpApps, - }; -}; - -const addAppSuccess = (state: State, action: Action): State => { - return { - ...state, - apps: [...state.apps, action.payload], - }; -}; - -const deleteApp = (state: State, action: Action): State => { - return { - ...state, - apps: [...state.apps].filter((app: App) => app.id !== action.payload), - }; -}; - -const updateApp = (state: State, action: Action): State => { - const appIdx = state.apps.findIndex((app) => app.id === action.payload.id); - - return { - ...state, - apps: [ - ...state.apps.slice(0, appIdx), - { - ...action.payload, - }, - ...state.apps.slice(appIdx + 1), - ], - }; -}; - -const reorderApps = (state: State, action: Action): State => { - return { - ...state, - apps: action.payload, - }; -}; - -const sortApps = (state: State, action: Action): State => { - const sortedApps = sortData(state.apps, action.payload); - - return { - ...state, - apps: sortedApps, - }; -}; - -const appReducer = (state = initialState, action: Action) => { - switch (action.type) { - case ActionTypes.getApps: - return getApps(state, action); - case ActionTypes.getAppsSuccess: - return getAppsSuccess(state, action); - case ActionTypes.getAppsError: - return getAppsError(state, action); - case ActionTypes.pinApp: - return pinApp(state, action); - case ActionTypes.addAppSuccess: - return addAppSuccess(state, action); - case ActionTypes.deleteApp: - return deleteApp(state, action); - case ActionTypes.updateApp: - return updateApp(state, action); - case ActionTypes.reorderApps: - return reorderApps(state, action); - case ActionTypes.sortApps: - return sortApps(state, action); - default: - return state; - } -}; - -export default appReducer; diff --git a/client/src/store/reducers/bookmark.ts b/client/src/store/reducers/bookmark.ts deleted file mode 100644 index a554d6e..0000000 --- a/client/src/store/reducers/bookmark.ts +++ /dev/null @@ -1,178 +0,0 @@ -import { ActionTypes, Action } from '../actions'; -import { Category, Bookmark } from '../../interfaces'; -import { sortData } from '../../utility'; - -export interface State { - loading: boolean; - errors: string | undefined; - categories: Category[]; -} - -const initialState: State = { - loading: true, - errors: undefined, - categories: [] -} - -const getCategories = (state: State, action: Action): State => { - return { - ...state, - loading: true, - errors: undefined - } -} - -const getCategoriesSuccess = (state: State, action: Action): State => { - return { - ...state, - loading: false, - categories: action.payload - } -} - -const addCategory = (state: State, action: Action): State => { - return { - ...state, - categories: [ - ...state.categories, - { - ...action.payload, - bookmarks: [] - } - ] - } -} - -const addBookmark = (state: State, action: Action): State => { - const categoryIndex = state.categories.findIndex((category: Category) => category.id === action.payload.categoryId); - - return { - ...state, - categories: [ - ...state.categories.slice(0, categoryIndex), - { - ...state.categories[categoryIndex], - bookmarks: [ - ...state.categories[categoryIndex].bookmarks, - { - ...action.payload - } - ] - }, - ...state.categories.slice(categoryIndex + 1) - ] - } -} - -const pinCategory = (state: State, action: Action): State => { - const tmpCategories = [...state.categories]; - const changedCategory = tmpCategories.find((category: Category) => category.id === action.payload.id); - - if (changedCategory) { - changedCategory.isPinned = action.payload.isPinned; - } - - return { - ...state, - categories: tmpCategories - } -} - -const deleteCategory = (state: State, action: Action): State => { - const categoryIndex = state.categories.findIndex((category: Category) => category.id === action.payload); - - return { - ...state, - categories: [ - ...state.categories.slice(0, categoryIndex), - ...state.categories.slice(categoryIndex + 1) - ] - } -} - -const updateCategory = (state: State, action: Action): State => { - const tmpCategories = [...state.categories]; - const categoryInUpdate = tmpCategories.find((category: Category) => category.id === action.payload.id); - - if (categoryInUpdate) { - categoryInUpdate.name = action.payload.name; - } - - return { - ...state, - categories: tmpCategories - } -} - -const deleteBookmark = (state: State, action: Action): State => { - const tmpCategories = [...state.categories]; - const categoryInUpdate = tmpCategories.find((category: Category) => category.id === action.payload.categoryId); - - if (categoryInUpdate) { - categoryInUpdate.bookmarks = categoryInUpdate.bookmarks.filter((bookmark: Bookmark) => bookmark.id !== action.payload.bookmarkId); - } - - - return { - ...state, - categories: tmpCategories - } -} - -const updateBookmark = (state: State, action: Action): State => { - let categoryIndex = state.categories.findIndex((category: Category) => category.id === action.payload.categoryId); - let bookmarkIndex = state.categories[categoryIndex].bookmarks.findIndex((bookmark: Bookmark) => bookmark.id === action.payload.id); - - return { - ...state, - categories: [ - ...state.categories.slice(0, categoryIndex), - { - ...state.categories[categoryIndex], - bookmarks: [ - ...state.categories[categoryIndex].bookmarks.slice(0, bookmarkIndex), - { - ...action.payload - }, - ...state.categories[categoryIndex].bookmarks.slice(bookmarkIndex + 1) - ] - }, - ...state.categories.slice(categoryIndex + 1) - ] - } -} - -const sortCategories = (state: State, action: Action): State => { - const sortedCategories = sortData(state.categories, action.payload); - - return { - ...state, - categories: sortedCategories - } -} - -const reorderCategories = (state: State, action: Action): State => { - return { - ...state, - categories: action.payload - } -} - -const bookmarkReducer = (state = initialState, action: Action) => { - switch (action.type) { - case ActionTypes.getCategories: return getCategories(state, action); - case ActionTypes.getCategoriesSuccess: return getCategoriesSuccess(state, action); - case ActionTypes.addCategory: return addCategory(state, action); - case ActionTypes.addBookmark: return addBookmark(state, action); - case ActionTypes.pinCategory: return pinCategory(state, action); - case ActionTypes.deleteCategory: return deleteCategory(state, action); - case ActionTypes.updateCategory: return updateCategory(state, action); - case ActionTypes.deleteBookmark: return deleteBookmark(state, action); - case ActionTypes.updateBookmark: return updateBookmark(state, action); - case ActionTypes.sortCategories: return sortCategories(state, action); - case ActionTypes.reorderCategories: return reorderCategories(state, action); - default: return state; - } -} - -export default bookmarkReducer; \ No newline at end of file diff --git a/client/src/store/reducers/config.ts b/client/src/store/reducers/config.ts index d216316..41c57d1 100644 --- a/client/src/store/reducers/config.ts +++ b/client/src/store/reducers/config.ts @@ -1,79 +1,58 @@ -import { ActionTypes, Action } from '../actions'; +import { Action } from '../actions'; +import { ActionType } from '../action-types'; import { Config, Query } from '../../interfaces'; import { configTemplate } from '../../utility'; -export interface State { +interface ConfigState { loading: boolean; config: Config; customQueries: Query[]; } -const initialState: State = { +const initialState: ConfigState = { loading: true, config: { ...configTemplate }, customQueries: [], }; -const getConfig = (state: State, action: Action): State => { - return { - ...state, - loading: false, - config: action.payload, - }; -}; - -const updateConfig = (state: State, action: Action): State => { - return { - ...state, - config: action.payload, - }; -}; - -const fetchQueries = (state: State, action: Action): State => { - return { - ...state, - customQueries: action.payload, - }; -}; - -const addQuery = (state: State, action: Action): State => { - return { - ...state, - customQueries: [...state.customQueries, action.payload], - }; -}; - -const deleteQuery = (state: State, action: Action): State => { - return { - ...state, - customQueries: action.payload, - }; -}; - -const updateQuery = (state: State, action: Action): State => { - return { - ...state, - customQueries: action.payload, - }; -}; - -const configReducer = (state: State = initialState, action: Action) => { +export const configReducer = ( + state: ConfigState = initialState, + action: Action +): ConfigState => { switch (action.type) { - case ActionTypes.getConfig: - return getConfig(state, action); - case ActionTypes.updateConfig: - return updateConfig(state, action); - case ActionTypes.fetchQueries: - return fetchQueries(state, action); - case ActionTypes.addQuery: - return addQuery(state, action); - case ActionTypes.deleteQuery: - return deleteQuery(state, action); - case ActionTypes.updateQuery: - return updateQuery(state, action); + case ActionType.getConfig: + return { + ...state, + loading: false, + config: action.payload, + }; + case ActionType.updateConfig: + return { + ...state, + config: action.payload, + }; + case ActionType.fetchQueries: + return { + ...state, + customQueries: action.payload, + }; + case ActionType.addQuery: + return { + ...state, + customQueries: [...state.customQueries, action.payload], + }; + case ActionType.deleteQuery: + return { + ...state, + customQueries: action.payload, + }; + case ActionType.updateQuery: + return { + ...state, + customQueries: action.payload, + }; + default: return state; } }; - -export default configReducer; diff --git a/client/src/store/reducers/index.ts b/client/src/store/reducers/index.ts index 96e9f95..f30ccd8 100644 --- a/client/src/store/reducers/index.ts +++ b/client/src/store/reducers/index.ts @@ -1,19 +1,13 @@ import { combineReducers } from 'redux'; -import { GlobalState } from '../../interfaces/GlobalState'; +import { themeReducer } from './theme'; +import { configReducer } from './config'; +import { notificationReducer } from './notification'; -import themeReducer from './theme'; -import appReducer from './app'; -import bookmarkReducer from './bookmark'; -import notificationReducer from './notification'; -import configReducer from './config'; - -const rootReducer = combineReducers({ +export const reducers = combineReducers({ theme: themeReducer, - app: appReducer, - bookmark: bookmarkReducer, + config: configReducer, notification: notificationReducer, - config: configReducer -}) +}); -export default rootReducer; \ No newline at end of file +export type State = ReturnType; diff --git a/client/src/store/reducers/notification.ts b/client/src/store/reducers/notification.ts index c0822b7..544402f 100644 --- a/client/src/store/reducers/notification.ts +++ b/client/src/store/reducers/notification.ts @@ -1,45 +1,42 @@ -import { ActionTypes, Action } from '../actions'; +import { Action } from '../actions'; +import { ActionType } from '../action-types'; import { Notification } from '../../interfaces'; -export interface State { +export interface NotificationState { notifications: Notification[]; idCounter: number; } -const initialState: State = { +const initialState: NotificationState = { notifications: [], - idCounter: 0 -} + idCounter: 0, +}; -const createNotification = (state: State, action: Action): State => { - const tmpNotifications = [...state.notifications, { - ...action.payload, - id: state.idCounter - }]; - - return { - ...state, - notifications: tmpNotifications, - idCounter: state.idCounter + 1 - } -} - -const clearNotification = (state: State, action: Action): State => { - const tmpNotifications = [...state.notifications] - .filter((notification: Notification) => notification.id !== action.payload); - - return { - ...state, - notifications: tmpNotifications - } -} - -const notificationReducer = (state = initialState, action: Action) => { +export const notificationReducer = ( + state: NotificationState = initialState, + action: Action +): NotificationState => { switch (action.type) { - case ActionTypes.createNotification: return createNotification(state, action); - case ActionTypes.clearNotification: return clearNotification(state, action); - default: return state; + case ActionType.createNotification: + return { + ...state, + notifications: [ + ...state.notifications, + { + ...action.payload, + id: state.idCounter, + }, + ], + idCounter: state.idCounter + 1, + }; + case ActionType.clearNotification: + return { + ...state, + notifications: [...state.notifications].filter( + (notification) => notification.id !== action.payload + ), + }; + default: + return state; } -} - -export default notificationReducer; \ No newline at end of file +}; diff --git a/client/src/store/reducers/theme.ts b/client/src/store/reducers/theme.ts index 6adc225..ef32495 100644 --- a/client/src/store/reducers/theme.ts +++ b/client/src/store/reducers/theme.ts @@ -1,11 +1,12 @@ -import { ActionTypes, Action } from '../actions'; +import { Action } from '../actions'; +import { ActionType } from '../action-types'; import { Theme } from '../../interfaces/Theme'; -export interface State { +interface ThemeState { theme: Theme; } -const initialState: State = { +const initialState: ThemeState = { theme: { name: 'tron', colors: { @@ -16,13 +17,14 @@ const initialState: State = { }, }; -const themeReducer = (state = initialState, action: Action) => { +export const themeReducer = ( + state: ThemeState = initialState, + action: Action +): ThemeState => { switch (action.type) { - case ActionTypes.setTheme: + case ActionType.setTheme: return { theme: action.payload }; default: return state; } }; - -export default themeReducer; diff --git a/client/src/store/store.ts b/client/src/store/store.ts index 22250a7..263407a 100644 --- a/client/src/store/store.ts +++ b/client/src/store/store.ts @@ -1,7 +1,10 @@ import { createStore, applyMiddleware } from 'redux'; import { composeWithDevTools } from 'redux-devtools-extension'; import thunk from 'redux-thunk'; -import rootReducer from './reducers'; -const initialState = {}; +import { reducers } from './reducers'; -export const store = createStore(rootReducer, initialState, composeWithDevTools(applyMiddleware(thunk))); \ No newline at end of file +export const store = createStore( + reducers, + {}, + composeWithDevTools(applyMiddleware(thunk)) +);