App state: refactored reducers and actions for config, theme and notifications

This commit is contained in:
Paweł Malak 2021-11-09 12:21:36 +01:00
parent d1738a0a3e
commit 7e89ab0204
20 changed files with 348 additions and 1293 deletions

View file

@ -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<GetConfigAction>) => {
try {
const res = await axios.get<ApiResponse<Config>>('/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<UpdateConfigAction>) => {
try {
const res = await axios.put<ApiResponse<Config>>('/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<FetchQueriesAction>) => {
try {
const res = await axios.get<ApiResponse<Query[]>>('/api/queries');
dispatch({
type: ActionType.fetchQueries,
payload: res.data.data,
});
} catch (err) {
console.log(err);
}
};
export const addQuery =
(query: Query) => async (dispatch: Dispatch<AddQueryAction>) => {
try {
const res = await axios.post<ApiResponse<Query>>('/api/queries', query);
dispatch({
type: ActionType.addQuery,
payload: res.data.data,
});
} catch (err) {
console.log(err);
}
};
export const deleteQuery =
(prefix: string) => async (dispatch: Dispatch<DeleteQueryAction>) => {
try {
const res = await axios.delete<ApiResponse<Query[]>>(
`/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<UpdateQueryAction>) => {
try {
const res = await axios.put<ApiResponse<Query[]>>(
`/api/queries/${oldPrefix}`,
query
);
dispatch({
type: ActionType.updateQuery,
payload: res.data.data,
});
} catch (err) {
console.log(err);
}
};

View file

@ -0,0 +1,3 @@
export * from './theme';
export * from './config';
export * from './notification';

View file

@ -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<CreateNotificationAction>) => {
dispatch({
type: ActionType.createNotification,
payload: notification,
});
};
export const clearNotification =
(id: number) => (dispatch: Dispatch<ClearNotificationAction>) => {
dispatch({
type: ActionType.clearNotification,
payload: id,
});
};

View file

@ -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<SetThemeAction>) => {
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);
}
};

View file

@ -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',
}

View file

@ -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<any>
| PinAppAction
| AddAppAction
| DeleteAppAction
| UpdateAppAction
| ReorderAppsAction
| SortAppsAction
// Categories
| GetCategoriesAction<any>
| AddCategoryAction
| PinCategoryAction
| DeleteCategoryAction
| UpdateCategoryAction
| SortCategoriesAction
| ReorderCategoriesAction
// Bookmarks
| AddBookmarkAction
| DeleteBookmarkAction
| UpdateBookmarkAction
// Notifications
| CreateNotificationAction
| ClearNotificationAction
// Config
| GetConfigAction
| UpdateConfigAction
| FetchQueriesAction
| AddQueryAction
| DeleteQueryAction
| UpdateQueryAction;

View file

@ -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<T> {
type:
| ActionTypes.getApps
| ActionTypes.getAppsSuccess
| ActionTypes.getAppsError;
payload: T;
}
export const getApps = () => async (dispatch: Dispatch) => {
dispatch<GetAppsAction<undefined>>({
type: ActionTypes.getApps,
payload: undefined,
});
try {
const res = await axios.get<ApiResponse<App[]>>('/api/apps');
dispatch<GetAppsAction<App[]>>({
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<ApiResponse<App>>(`/api/apps/${id}`, {
isPinned: !isPinned,
});
const status = isPinned
? 'unpinned from Homescreen'
: 'pinned to Homescreen';
dispatch<CreateNotificationAction>({
type: ActionTypes.createNotification,
payload: {
title: 'Success',
message: `App ${name} ${status}`,
},
});
dispatch<PinAppAction>({
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<ApiResponse<App>>('/api/apps', formData);
dispatch<CreateNotificationAction>({
type: ActionTypes.createNotification,
payload: {
title: 'Success',
message: `App added`,
},
});
await dispatch<AddAppAction>({
type: ActionTypes.addAppSuccess,
payload: res.data.data,
});
// Sort apps
dispatch<any>(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<ApiResponse<{}>>(`/api/apps/${id}`);
dispatch<CreateNotificationAction>({
type: ActionTypes.createNotification,
payload: {
title: 'Success',
message: 'App deleted',
},
});
dispatch<DeleteAppAction>({
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<ApiResponse<App>>(
`/api/apps/${id}`,
formData
);
dispatch<CreateNotificationAction>({
type: ActionTypes.createNotification,
payload: {
title: 'Success',
message: `App updated`,
},
});
await dispatch<UpdateAppAction>({
type: ActionTypes.updateApp,
payload: res.data.data,
});
// Sort apps
dispatch<any>(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<ApiResponse<{}>>('/api/apps/0/reorder', updateQuery);
dispatch<ReorderAppsAction>({
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<ApiResponse<Config>>('/api/config');
dispatch<SortAppsAction>({
type: ActionTypes.sortApps,
payload: res.data.data.useOrdering,
});
} catch (err) {
console.log(err);
}
};

View file

@ -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<T> {
type:
| ActionTypes.getCategories
| ActionTypes.getCategoriesSuccess
| ActionTypes.getCategoriesError;
payload: T;
}
export const getCategories = () => async (dispatch: Dispatch) => {
dispatch<GetCategoriesAction<undefined>>({
type: ActionTypes.getCategories,
payload: undefined,
});
try {
const res = await axios.get<ApiResponse<Category[]>>('/api/categories');
dispatch<GetCategoriesAction<Category[]>>({
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<ApiResponse<Category>>(
'/api/categories',
formData
);
dispatch<CreateNotificationAction>({
type: ActionTypes.createNotification,
payload: {
title: 'Success',
message: `Category ${formData.name} created`,
},
});
dispatch<AddCategoryAction>({
type: ActionTypes.addCategory,
payload: res.data.data,
});
dispatch<any>(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<ApiResponse<Bookmark>>(
'/api/bookmarks',
formData
);
dispatch<CreateNotificationAction>({
type: ActionTypes.createNotification,
payload: {
title: 'Success',
message: `Bookmark created`,
},
});
dispatch<AddBookmarkAction>({
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<ApiResponse<Category>>(
`/api/categories/${id}`,
{ isPinned: !isPinned }
);
const status = isPinned
? 'unpinned from Homescreen'
: 'pinned to Homescreen';
dispatch<CreateNotificationAction>({
type: ActionTypes.createNotification,
payload: {
title: 'Success',
message: `Category ${name} ${status}`,
},
});
dispatch<PinCategoryAction>({
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<ApiResponse<{}>>(`/api/categories/${id}`);
dispatch<CreateNotificationAction>({
type: ActionTypes.createNotification,
payload: {
title: 'Success',
message: `Category deleted`,
},
});
dispatch<DeleteCategoryAction>({
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<ApiResponse<Category>>(
`/api/categories/${id}`,
formData
);
dispatch<CreateNotificationAction>({
type: ActionTypes.createNotification,
payload: {
title: 'Success',
message: `Category ${formData.name} updated`,
},
});
dispatch<UpdateCategoryAction>({
type: ActionTypes.updateCategory,
payload: res.data.data,
});
dispatch<any>(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<ApiResponse<{}>>(`/api/bookmarks/${bookmarkId}`);
dispatch<CreateNotificationAction>({
type: ActionTypes.createNotification,
payload: {
title: 'Success',
message: 'Bookmark deleted',
},
});
dispatch<DeleteBookmarkAction>({
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<ApiResponse<Bookmark>>(
`/api/bookmarks/${bookmarkId}`,
formData
);
dispatch<CreateNotificationAction>({
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<DeleteBookmarkAction>({
type: ActionTypes.deleteBookmark,
payload: {
bookmarkId,
categoryId: category.prev,
},
});
// Add bookmark to the new category
dispatch<AddBookmarkAction>({
type: ActionTypes.addBookmark,
payload: res.data.data,
});
} else {
// Else update only name/url/icon
dispatch<UpdateBookmarkAction>({
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<ApiResponse<Config>>('/api/config');
dispatch<SortCategoriesAction>({
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<ApiResponse<{}>>(
'/api/categories/0/reorder',
updateQuery
);
dispatch<ReorderCategoriesAction>({
type: ActionTypes.reorderCategories,
payload: categories,
});
} catch (err) {
console.log(err);
}
};

View file

@ -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<ApiResponse<Config>>('/api/config');
dispatch<GetConfigAction>({
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<ApiResponse<Config>>('/api/config', formData);
dispatch<CreateNotificationAction>({
type: ActionTypes.createNotification,
payload: {
title: 'Success',
message: 'Settings updated',
},
});
dispatch<UpdateConfigAction>({
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<FetchQueriesAction>) => {
try {
const res = await axios.get<ApiResponse<Query[]>>('/api/queries');
dispatch<FetchQueriesAction>({
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<AddQueryAction>) => {
try {
const res = await axios.post<ApiResponse<Query>>('/api/queries', query);
dispatch<AddQueryAction>({
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<DeleteQueryAction>) => {
try {
const res = await axios.delete<ApiResponse<Query[]>>(
`/api/queries/${prefix}`
);
dispatch<DeleteQueryAction>({
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<UpdateQueryAction>) => {
try {
const res = await axios.put<ApiResponse<Query[]>>(
`/api/queries/${oldPrefix}`,
query
);
dispatch<UpdateQueryAction>({
type: ActionTypes.updateQuery,
payload: res.data.data,
});
} catch (err) {
console.log(err);
}
};

View file

@ -1,6 +1,27 @@
export * from './theme';
export * from './app';
export * from './actionTypes';
export * from './bookmark';
export * from './notification';
export * from './config';
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;

View file

@ -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<CreateNotificationAction>({
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<ClearNotificationAction>({
type: ActionTypes.clearNotification,
payload: id
})
}

View file

@ -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<SetThemeAction>({
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);
}
}

View file

@ -0,0 +1,2 @@
export * from './store';
export * as actionCreators from './action-creators';

View file

@ -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<App>(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;

View file

@ -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<Category>(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;

View file

@ -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;

View file

@ -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<GlobalState>({
export const reducers = combineReducers({
theme: themeReducer,
app: appReducer,
bookmark: bookmarkReducer,
config: configReducer,
notification: notificationReducer,
config: configReducer
})
});
export default rootReducer;
export type State = ReturnType<typeof reducers>;

View file

@ -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;
};

View file

@ -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;

View file

@ -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)));
export const store = createStore(
reducers,
{},
composeWithDevTools(applyMiddleware(thunk))
);