mirror of
https://github.com/pawelmalak/flame.git
synced 2025-07-23 13:29:35 +02:00
App state: refactored reducers and actions for config, theme and notifications
This commit is contained in:
parent
d1738a0a3e
commit
7e89ab0204
20 changed files with 348 additions and 1293 deletions
141
client/src/store/action-creators/config.ts
Normal file
141
client/src/store/action-creators/config.ts
Normal 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);
|
||||||
|
}
|
||||||
|
};
|
3
client/src/store/action-creators/index.ts
Normal file
3
client/src/store/action-creators/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export * from './theme';
|
||||||
|
export * from './config';
|
||||||
|
export * from './notification';
|
24
client/src/store/action-creators/notification.ts
Normal file
24
client/src/store/action-creators/notification.ts
Normal 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,
|
||||||
|
});
|
||||||
|
};
|
26
client/src/store/action-creators/theme.ts
Normal file
26
client/src/store/action-creators/theme.ts
Normal 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);
|
||||||
|
}
|
||||||
|
};
|
14
client/src/store/action-types/index.ts
Normal file
14
client/src/store/action-types/index.ts
Normal 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',
|
||||||
|
}
|
|
@ -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;
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,157 +1,32 @@
|
||||||
import axios from 'axios';
|
import { ActionType } from '../action-types';
|
||||||
import { Dispatch } from 'redux';
|
import { Config, Query } from '../../interfaces';
|
||||||
import { ActionTypes } from './actionTypes';
|
|
||||||
import { Config, ApiResponse, Query } from '../../interfaces';
|
|
||||||
import { CreateNotificationAction } from './notification';
|
|
||||||
import { storeUIConfig } from '../../utility';
|
|
||||||
|
|
||||||
export interface GetConfigAction {
|
export interface GetConfigAction {
|
||||||
type: ActionTypes.getConfig;
|
type: ActionType.getConfig;
|
||||||
payload: Config;
|
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 {
|
export interface UpdateConfigAction {
|
||||||
type: ActionTypes.updateConfig;
|
type: ActionType.updateConfig;
|
||||||
payload: Config;
|
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 {
|
export interface FetchQueriesAction {
|
||||||
type: ActionTypes.fetchQueries;
|
type: ActionType.fetchQueries;
|
||||||
payload: Query[];
|
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 {
|
export interface AddQueryAction {
|
||||||
type: ActionTypes.addQuery;
|
type: ActionType.addQuery;
|
||||||
payload: Query;
|
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 {
|
export interface DeleteQueryAction {
|
||||||
type: ActionTypes.deleteQuery;
|
type: ActionType.deleteQuery;
|
||||||
payload: Query[];
|
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 {
|
export interface UpdateQueryAction {
|
||||||
type: ActionTypes.updateQuery;
|
type: ActionType.updateQuery;
|
||||||
payload: Query[];
|
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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,6 +1,27 @@
|
||||||
export * from './theme';
|
import { SetThemeAction } from './theme';
|
||||||
export * from './app';
|
import {
|
||||||
export * from './actionTypes';
|
AddQueryAction,
|
||||||
export * from './bookmark';
|
DeleteQueryAction,
|
||||||
export * from './notification';
|
FetchQueriesAction,
|
||||||
export * from './config';
|
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;
|
||||||
|
|
|
@ -1,27 +1,12 @@
|
||||||
import { Dispatch } from 'redux';
|
import { ActionType } from '../action-types';
|
||||||
import { ActionTypes } from '.';
|
|
||||||
import { NewNotification } from '../../interfaces';
|
import { NewNotification } from '../../interfaces';
|
||||||
|
|
||||||
export interface CreateNotificationAction {
|
export interface CreateNotificationAction {
|
||||||
type: ActionTypes.createNotification,
|
type: ActionType.createNotification;
|
||||||
payload: NewNotification
|
payload: NewNotification;
|
||||||
}
|
|
||||||
|
|
||||||
export const createNotification = (notification: NewNotification) => (dispatch: Dispatch) => {
|
|
||||||
dispatch<CreateNotificationAction>({
|
|
||||||
type: ActionTypes.createNotification,
|
|
||||||
payload: notification
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ClearNotificationAction {
|
export interface ClearNotificationAction {
|
||||||
type: ActionTypes.clearNotification,
|
type: ActionType.clearNotification;
|
||||||
payload: number
|
payload: number;
|
||||||
}
|
|
||||||
|
|
||||||
export const clearNotification = (id: number) => (dispatch: Dispatch) => {
|
|
||||||
dispatch<ClearNotificationAction>({
|
|
||||||
type: ActionTypes.clearNotification,
|
|
||||||
payload: id
|
|
||||||
})
|
|
||||||
}
|
}
|
|
@ -1,29 +1,7 @@
|
||||||
import { Dispatch } from 'redux';
|
import { ActionType } from '../action-types';
|
||||||
import { themes } from '../../components/Themer/themes.json';
|
import { Theme } from '../../interfaces';
|
||||||
import { Theme } from '../../interfaces/Theme';
|
|
||||||
import { ActionTypes } from './actionTypes';
|
|
||||||
|
|
||||||
export interface SetThemeAction {
|
export interface SetThemeAction {
|
||||||
type: ActionTypes.setTheme,
|
type: ActionType.setTheme;
|
||||||
payload: Theme
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
2
client/src/store/index.ts
Normal file
2
client/src/store/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './store';
|
||||||
|
export * as actionCreators from './action-creators';
|
|
@ -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;
|
|
|
@ -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;
|
|
|
@ -1,79 +1,58 @@
|
||||||
import { ActionTypes, Action } from '../actions';
|
import { Action } from '../actions';
|
||||||
|
import { ActionType } from '../action-types';
|
||||||
import { Config, Query } from '../../interfaces';
|
import { Config, Query } from '../../interfaces';
|
||||||
import { configTemplate } from '../../utility';
|
import { configTemplate } from '../../utility';
|
||||||
|
|
||||||
export interface State {
|
interface ConfigState {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
config: Config;
|
config: Config;
|
||||||
customQueries: Query[];
|
customQueries: Query[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: State = {
|
const initialState: ConfigState = {
|
||||||
loading: true,
|
loading: true,
|
||||||
config: { ...configTemplate },
|
config: { ...configTemplate },
|
||||||
customQueries: [],
|
customQueries: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const getConfig = (state: State, action: Action): State => {
|
export const configReducer = (
|
||||||
|
state: ConfigState = initialState,
|
||||||
|
action: Action
|
||||||
|
): ConfigState => {
|
||||||
|
switch (action.type) {
|
||||||
|
case ActionType.getConfig:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
loading: false,
|
loading: false,
|
||||||
config: action.payload,
|
config: action.payload,
|
||||||
};
|
};
|
||||||
};
|
case ActionType.updateConfig:
|
||||||
|
|
||||||
const updateConfig = (state: State, action: Action): State => {
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
config: action.payload,
|
config: action.payload,
|
||||||
};
|
};
|
||||||
};
|
case ActionType.fetchQueries:
|
||||||
|
|
||||||
const fetchQueries = (state: State, action: Action): State => {
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
customQueries: action.payload,
|
customQueries: action.payload,
|
||||||
};
|
};
|
||||||
};
|
case ActionType.addQuery:
|
||||||
|
|
||||||
const addQuery = (state: State, action: Action): State => {
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
customQueries: [...state.customQueries, action.payload],
|
customQueries: [...state.customQueries, action.payload],
|
||||||
};
|
};
|
||||||
};
|
case ActionType.deleteQuery:
|
||||||
|
|
||||||
const deleteQuery = (state: State, action: Action): State => {
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
customQueries: action.payload,
|
customQueries: action.payload,
|
||||||
};
|
};
|
||||||
};
|
case ActionType.updateQuery:
|
||||||
|
|
||||||
const updateQuery = (state: State, action: Action): State => {
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
customQueries: action.payload,
|
customQueries: action.payload,
|
||||||
};
|
};
|
||||||
};
|
|
||||||
|
|
||||||
const configReducer = (state: State = initialState, action: Action) => {
|
|
||||||
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);
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default configReducer;
|
|
||||||
|
|
|
@ -1,19 +1,13 @@
|
||||||
import { combineReducers } from 'redux';
|
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';
|
export const reducers = combineReducers({
|
||||||
import appReducer from './app';
|
|
||||||
import bookmarkReducer from './bookmark';
|
|
||||||
import notificationReducer from './notification';
|
|
||||||
import configReducer from './config';
|
|
||||||
|
|
||||||
const rootReducer = combineReducers<GlobalState>({
|
|
||||||
theme: themeReducer,
|
theme: themeReducer,
|
||||||
app: appReducer,
|
config: configReducer,
|
||||||
bookmark: bookmarkReducer,
|
|
||||||
notification: notificationReducer,
|
notification: notificationReducer,
|
||||||
config: configReducer
|
});
|
||||||
})
|
|
||||||
|
|
||||||
export default rootReducer;
|
export type State = ReturnType<typeof reducers>;
|
||||||
|
|
|
@ -1,45 +1,42 @@
|
||||||
import { ActionTypes, Action } from '../actions';
|
import { Action } from '../actions';
|
||||||
|
import { ActionType } from '../action-types';
|
||||||
import { Notification } from '../../interfaces';
|
import { Notification } from '../../interfaces';
|
||||||
|
|
||||||
export interface State {
|
export interface NotificationState {
|
||||||
notifications: Notification[];
|
notifications: Notification[];
|
||||||
idCounter: number;
|
idCounter: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: State = {
|
const initialState: NotificationState = {
|
||||||
notifications: [],
|
notifications: [],
|
||||||
idCounter: 0
|
idCounter: 0,
|
||||||
}
|
};
|
||||||
|
|
||||||
const createNotification = (state: State, action: Action): State => {
|
export const notificationReducer = (
|
||||||
const tmpNotifications = [...state.notifications, {
|
state: NotificationState = initialState,
|
||||||
...action.payload,
|
action: Action
|
||||||
id: state.idCounter
|
): NotificationState => {
|
||||||
}];
|
|
||||||
|
|
||||||
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) => {
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case ActionTypes.createNotification: return createNotification(state, action);
|
case ActionType.createNotification:
|
||||||
case ActionTypes.clearNotification: return clearNotification(state, action);
|
return {
|
||||||
default: return state;
|
...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;
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { ActionTypes, Action } from '../actions';
|
import { Action } from '../actions';
|
||||||
|
import { ActionType } from '../action-types';
|
||||||
import { Theme } from '../../interfaces/Theme';
|
import { Theme } from '../../interfaces/Theme';
|
||||||
|
|
||||||
export interface State {
|
interface ThemeState {
|
||||||
theme: Theme;
|
theme: Theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: State = {
|
const initialState: ThemeState = {
|
||||||
theme: {
|
theme: {
|
||||||
name: 'tron',
|
name: 'tron',
|
||||||
colors: {
|
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) {
|
switch (action.type) {
|
||||||
case ActionTypes.setTheme:
|
case ActionType.setTheme:
|
||||||
return { theme: action.payload };
|
return { theme: action.payload };
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default themeReducer;
|
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
import { createStore, applyMiddleware } from 'redux';
|
import { createStore, applyMiddleware } from 'redux';
|
||||||
import { composeWithDevTools } from 'redux-devtools-extension';
|
import { composeWithDevTools } from 'redux-devtools-extension';
|
||||||
import thunk from 'redux-thunk';
|
import thunk from 'redux-thunk';
|
||||||
import rootReducer from './reducers';
|
import { reducers } from './reducers';
|
||||||
const initialState = {};
|
|
||||||
|
|
||||||
export const store = createStore(rootReducer, initialState, composeWithDevTools(applyMiddleware(thunk)));
|
export const store = createStore(
|
||||||
|
reducers,
|
||||||
|
{},
|
||||||
|
composeWithDevTools(applyMiddleware(thunk))
|
||||||
|
);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue