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 { 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);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
})
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
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 { 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;
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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))
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue