init: add fuse-react v8.3.5 skeleton

This commit is contained in:
2023-05-30 21:07:44 +01:00
commit 3a760d2646
543 changed files with 102541 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
import { createSlice } from '@reduxjs/toolkit';
const dialogSlice = createSlice({
name: 'dialog',
initialState: {
state: false,
options: {
children: 'Hi',
},
},
reducers: {
openDialog: (state, action) => {
state.state = true;
state.options = action.payload;
},
closeDialog: (state, action) => {
state.state = false;
},
},
});
export const { openDialog, closeDialog } = dialogSlice.actions;
export const selectFuseDialogState = ({ fuse }) => fuse.dialog.state;
export const selectFuseDialogOptions = ({ fuse }) => fuse.dialog.options;
export default dialogSlice.reducer;

View File

@@ -0,0 +1,16 @@
import { combineReducers } from '@reduxjs/toolkit';
import dialog from './dialogSlice';
import message from './messageSlice';
import navbar from './navbarSlice';
import navigation from './navigationSlice';
import settings from './settingsSlice';
const fuseReducers = combineReducers({
navigation,
settings,
navbar,
message,
dialog,
});
export default fuseReducers;

View File

@@ -0,0 +1,38 @@
import { createSlice } from '@reduxjs/toolkit';
const initialState = {
state: null,
options: {
anchorOrigin: {
vertical: 'top',
horizontal: 'center',
},
autoHideDuration: 2000,
message: 'Hi',
variant: null,
},
};
const messageSlice = createSlice({
name: 'message',
initialState,
reducers: {
showMessage: (state, action) => {
state.state = true;
state.options = {
...initialState.options,
...action.payload,
};
},
hideMessage: (state, action) => {
state.state = null;
},
},
});
export const { hideMessage, showMessage } = messageSlice.actions;
export const selectFuseMessageState = ({ fuse }) => fuse.message.state;
export const selectFuseMessageOptions = ({ fuse }) => fuse.message.options;
export default messageSlice.reducer;

View File

@@ -0,0 +1,54 @@
import { createSlice } from '@reduxjs/toolkit';
const navbarSlice = createSlice({
name: 'navbar',
initialState: {
open: true,
mobileOpen: false,
},
reducers: {
navbarToggleFolded: (state, action) => {
state.foldedOpen = !state.foldedOpen;
},
navbarOpenFolded: (state, action) => {
state.foldedOpen = true;
},
navbarCloseFolded: (state, action) => {
state.foldedOpen = false;
},
navbarToggleMobile: (state, action) => {
state.mobileOpen = !state.mobileOpen;
},
navbarOpenMobile: (state, action) => {
state.mobileOpen = true;
},
navbarCloseMobile: (state, action) => {
state.mobileOpen = false;
},
navbarClose: (state, action) => {
state.open = false;
},
navbarOpen: (state, action) => {
state.open = true;
},
navbarToggle: (state, action) => {
state.open = !state.open;
},
},
});
export const {
navbarToggleFolded,
navbarOpenFolded,
navbarCloseFolded,
navbarOpen,
navbarClose,
navbarToggle,
navbarOpenMobile,
navbarCloseMobile,
navbarToggleMobile,
} = navbarSlice.actions;
export const selectFuseNavbar = ({ fuse }) => fuse.navbar;
export default navbarSlice.reducer;

View File

@@ -0,0 +1,96 @@
import { createEntityAdapter, createSelector, createSlice } from '@reduxjs/toolkit';
import navigationConfig from 'app/configs/navigationConfig';
import FuseUtils from '@fuse/utils';
import i18next from 'i18next';
import _ from '@lodash';
const navigationAdapter = createEntityAdapter();
const emptyInitialState = navigationAdapter.getInitialState();
const initialState = navigationAdapter.upsertMany(emptyInitialState, navigationConfig);
export const appendNavigationItem = (item, parentId) => (dispatch, getState) => {
const navigation = selectNavigationAll(getState());
return dispatch(setNavigation(FuseUtils.appendNavItem(navigation, item, parentId)));
};
export const prependNavigationItem = (item, parentId) => (dispatch, getState) => {
const navigation = selectNavigationAll(getState());
return dispatch(setNavigation(FuseUtils.prependNavItem(navigation, item, parentId)));
};
export const updateNavigationItem = (id, item) => (dispatch, getState) => {
const navigation = selectNavigationAll(getState());
return dispatch(setNavigation(FuseUtils.updateNavItem(navigation, id, item)));
};
export const removeNavigationItem = (id) => (dispatch, getState) => {
const navigation = selectNavigationAll(getState());
return dispatch(setNavigation(FuseUtils.removeNavItem(navigation, id)));
};
export const {
selectAll: selectNavigationAll,
selectIds: selectNavigationIds,
selectById: selectNavigationItemById,
} = navigationAdapter.getSelectors((state) => state.fuse.navigation);
const navigationSlice = createSlice({
name: 'navigation',
initialState,
reducers: {
setNavigation: navigationAdapter.setAll,
resetNavigation: (state, action) => initialState,
},
});
export const { setNavigation, resetNavigation } = navigationSlice.actions;
const getUserRole = (state) => state.user.role;
export const selectNavigation = createSelector(
[selectNavigationAll, ({ i18n }) => i18n.language, getUserRole],
(navigation, language, userRole) => {
function setTranslationValues(data) {
// loop through every object in the array
return data.map((item) => {
if (item.translate && item.title) {
item.title = i18next.t(`navigation:${item.translate}`);
}
// see if there is a children node
if (item.children) {
// run this function recursively on the children array
item.children = setTranslationValues(item.children);
}
return item;
});
}
return setTranslationValues(
_.merge(
[],
filterRecursively(navigation, (item) => FuseUtils.hasPermission(item.auth, userRole))
)
);
}
);
function filterRecursively(arr, predicate) {
return arr.filter(predicate).map((item) => {
item = { ...item };
if (item.children) {
item.children = filterRecursively(item.children, predicate);
}
return item;
});
}
export const selectFlatNavigation = createSelector([selectNavigation], (navigation) =>
FuseUtils.getFlatNavigation(navigation)
);
export default navigationSlice.reducer;

View File

@@ -0,0 +1,239 @@
import { createTheme, getContrastRatio } from '@mui/material/styles';
import { createAsyncThunk, createSelector, createSlice } from '@reduxjs/toolkit';
import _ from '@lodash';
import {
defaultSettings,
defaultThemeOptions,
extendThemeWithMixins,
getParsedQuerySettings,
mustHaveThemeOptions,
} from '@fuse/default-settings';
import settingsConfig from 'app/configs/settingsConfig';
import themeLayoutConfigs from 'app/theme-layouts/themeLayoutConfigs';
import { setUser, updateUserSettings } from 'app/store/userSlice';
import { darkPaletteText, lightPaletteText } from 'app/configs/themesConfig';
export const changeFuseTheme = (theme) => (dispatch, getState) => {
const { fuse } = getState();
const { settings } = fuse;
const newSettings = {
...settings.current,
theme: {
main: theme,
navbar: theme,
toolbar: theme,
footer: theme,
},
};
dispatch(setDefaultSettings(newSettings));
};
function getInitialSettings() {
const defaultLayoutStyle =
settingsConfig.layout && settingsConfig.layout.style ? settingsConfig.layout.style : 'layout1';
const layout = {
style: defaultLayoutStyle,
config: themeLayoutConfigs[defaultLayoutStyle].defaults,
};
return _.merge({}, defaultSettings, { layout }, settingsConfig, getParsedQuerySettings());
}
export function generateSettings(_defaultSettings, _newSettings) {
const response = _.merge(
{},
_defaultSettings,
{ layout: { config: themeLayoutConfigs[_newSettings?.layout?.style]?.defaults } },
_newSettings
);
return response;
}
const initialSettings = getInitialSettings();
const initialState = {
initial: initialSettings,
defaults: _.merge({}, initialSettings),
current: _.merge({}, initialSettings),
};
export const setDefaultSettings = createAsyncThunk(
'fuse/settings/setDefaultSettings',
async (val, { dispatch, getState }) => {
const { fuse } = getState();
const { settings } = fuse;
const defaults = generateSettings(settings.defaults, val);
dispatch(updateUserSettings(defaults));
return {
...settings,
defaults: _.merge({}, defaults),
current: _.merge({}, defaults),
};
}
);
const settingsSlice = createSlice({
name: 'settings',
initialState,
reducers: {
setSettings: (state, action) => {
const current = generateSettings(state.defaults, action.payload);
return {
...state,
current,
};
},
setInitialSettings: (state, action) => {
return _.merge({}, initialState);
},
resetSettings: (state, action) => {
return {
...state,
defaults: _.merge({}, state.defaults),
current: _.merge({}, state.defaults),
};
},
},
extraReducers: {
[setDefaultSettings.fulfilled]: (state, action) => action.payload,
[setUser.fulfilled]: (state, action) => {
const defaults = generateSettings(state.defaults, action.payload?.data?.settings);
return {
...state,
defaults: _.merge({}, defaults),
current: _.merge({}, defaults),
};
},
},
});
const getDirection = (state) => state.fuse.settings.current.direction;
const getMainTheme = (state) => state.fuse.settings.current.theme.main;
const getNavbarTheme = (state) => state.fuse.settings.current.theme.navbar;
const getToolbarTheme = (state) => state.fuse.settings.current.theme.toolbar;
const getFooterTheme = (state) => state.fuse.settings.current.theme.footer;
function generateMuiTheme(theme, direction) {
const data = _.merge({}, defaultThemeOptions, theme, mustHaveThemeOptions);
const response = createTheme(
_.merge({}, data, {
mixins: extendThemeWithMixins(data),
direction,
})
);
return response;
}
export const selectContrastMainTheme = (bgColor) => {
function isDark(color) {
return getContrastRatio(color, '#ffffff') >= 3;
}
return isDark(bgColor) ? selectMainThemeDark : selectMainThemeLight;
};
function changeThemeMode(theme, mode) {
const modes = {
dark: {
palette: {
mode: 'dark',
divider: 'rgba(241,245,249,.12)',
background: {
paper: '#1E2125',
default: '#121212',
},
text: darkPaletteText,
},
},
light: {
palette: {
mode: 'light',
divider: '#e2e8f0',
background: {
paper: '#FFFFFF',
default: '#F7F7F7',
},
text: lightPaletteText,
},
},
};
return _.merge({}, theme, modes[mode]);
}
export const selectMainTheme = createSelector(
[getMainTheme, getDirection],
(theme, direction, id) => generateMuiTheme(theme, direction)
);
export const selectMainThemeDark = createSelector(
[getMainTheme, getDirection],
(theme, direction) => generateMuiTheme(changeThemeMode(theme, 'dark'), direction)
);
export const selectMainThemeLight = createSelector(
[getMainTheme, getDirection],
(theme, direction) => generateMuiTheme(changeThemeMode(theme, 'light'), direction)
);
export const selectNavbarTheme = createSelector(
[getNavbarTheme, getDirection],
(theme, direction) => generateMuiTheme(theme, direction)
);
export const selectNavbarThemeDark = createSelector(
[getNavbarTheme, getDirection],
(theme, direction) => generateMuiTheme(changeThemeMode(theme, 'dark'), direction)
);
export const selectNavbarThemeLight = createSelector(
[getNavbarTheme, getDirection],
(theme, direction) => generateMuiTheme(changeThemeMode(theme, 'light'), direction)
);
export const selectToolbarTheme = createSelector(
[getToolbarTheme, getDirection],
(theme, direction) => generateMuiTheme(theme, direction)
);
export const selectToolbarThemeDark = createSelector(
[getToolbarTheme, getDirection],
(theme, direction) => generateMuiTheme(changeThemeMode(theme, 'dark'), direction)
);
export const selectToolbarThemeLight = createSelector(
[getToolbarTheme, getDirection],
(theme, direction) => generateMuiTheme(changeThemeMode(theme, 'light'), direction)
);
export const selectFooterTheme = createSelector(
[getFooterTheme, getDirection],
(theme, direction) => generateMuiTheme(theme, direction)
);
export const selectFooterThemeDark = createSelector(
[getFooterTheme, getDirection],
(theme, direction) => generateMuiTheme(changeThemeMode(theme, 'dark'), direction)
);
export const selectFooterThemeLight = createSelector(
[getFooterTheme, getDirection],
(theme, direction) => generateMuiTheme(changeThemeMode(theme, 'light'), direction)
);
export const selectFuseCurrentSettings = ({ fuse }) => fuse.settings.current;
export const selectFuseCurrentLayoutConfig = ({ fuse }) => fuse.settings.current.layout.config;
export const selectFuseDefaultSettings = ({ fuse }) => fuse.settings.defaults;
export const selectFuseThemesSettings = ({ fuse }) => fuse.settings.themes;
export const { resetSettings, setInitialSettings, setSettings } = settingsSlice.actions;
export default settingsSlice.reducer;

View File

@@ -0,0 +1,57 @@
import { createSelector, createSlice } from '@reduxjs/toolkit';
import i18n from 'src/i18n';
import { setDefaultSettings } from './fuse/settingsSlice';
export const changeLanguage = (languageId) => (dispatch, getState) => {
const { direction } = getState().fuse.settings.defaults;
const newLangDirection = i18n.dir(languageId);
/*
If necessary, change theme direction
*/
if (newLangDirection !== direction) {
dispatch(setDefaultSettings({ direction: newLangDirection }));
}
/*
Change Language
*/
return i18n.changeLanguage(languageId).then(() => {
dispatch(i18nSlice.actions.languageChanged(languageId));
});
};
const i18nSlice = createSlice({
name: 'i18n',
initialState: {
language: i18n.options.lng,
languages: [
{ id: 'en', title: 'English', flag: 'US' },
{ id: 'tr', title: 'Turkish', flag: 'TR' },
{ id: 'ar', title: 'Arabic', flag: 'SA' },
],
},
reducers: {
languageChanged: (state, action) => {
state.language = action.payload;
},
},
});
export const selectCurrentLanguageId = ({ i18n: _i18n }) => _i18n.language;
export const selectLanguages = ({ i18n: _i18n }) => _i18n.languages;
export const selectCurrentLanguageDirection = createSelector([selectCurrentLanguageId], (id) => {
return i18n.dir(id);
});
export const selectCurrentLanguage = createSelector(
[selectCurrentLanguageId, selectLanguages],
(id, languages) => {
return languages.find((lng) => lng.id === id);
}
);
export default i18nSlice.reducer;

41
src/app/store/index.js Normal file
View File

@@ -0,0 +1,41 @@
import { configureStore } from '@reduxjs/toolkit';
import createReducer from './rootReducer';
if (process.env.NODE_ENV === 'development' && module.hot) {
module.hot.accept('./rootReducer', () => {
const newRootReducer = require('./rootReducer').default;
store.replaceReducer(newRootReducer.createReducer());
});
}
const middlewares = [];
if (process.env.NODE_ENV === 'development') {
const { createLogger } = require(`redux-logger`);
const logger = createLogger({ collapsed: (getState, action, logEntry) => !logEntry.error });
middlewares.push(logger);
}
const store = configureStore({
reducer: createReducer(),
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
immutableCheck: false,
serializableCheck: false,
}).concat(middlewares),
devTools: process.env.NODE_ENV === 'development',
});
store.asyncReducers = {};
export const injectReducer = (key, reducer) => {
if (store.asyncReducers[key]) {
return false;
}
store.asyncReducers[key] = reducer;
store.replaceReducer(createReducer(store.asyncReducers));
return store;
};
export default store;

View File

@@ -0,0 +1,24 @@
import { combineReducers } from '@reduxjs/toolkit';
import fuse from './fuse';
import i18n from './i18nSlice';
import user from './userSlice';
const createReducer = (asyncReducers) => (state, action) => {
const combinedReducer = combineReducers({
fuse,
i18n,
user,
...asyncReducers,
});
/*
Reset the redux store when user logged out
*/
if (action.type === 'user/userLoggedOut') {
// state = undefined;
}
return combinedReducer(state, action);
};
export default createReducer;

113
src/app/store/userSlice.js Normal file
View File

@@ -0,0 +1,113 @@
/* eslint import/no-extraneous-dependencies: off */
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import history from '@history';
import _ from '@lodash';
import { setInitialSettings } from 'app/store/fuse/settingsSlice';
import { showMessage } from 'app/store/fuse/messageSlice';
import settingsConfig from 'app/configs/settingsConfig';
import jwtService from '../auth/services/jwtService';
export const setUser = createAsyncThunk('user/setUser', async (user, { dispatch, getState }) => {
/*
You can redirect the logged-in user to a specific route depending on his role
*/
if (user.loginRedirectUrl) {
settingsConfig.loginRedirectUrl = user.loginRedirectUrl; // for example 'apps/academy'
}
return user;
});
export const updateUserSettings = createAsyncThunk(
'user/updateSettings',
async (settings, { dispatch, getState }) => {
const { user } = getState();
const newUser = _.merge({}, user, { data: { settings } });
dispatch(updateUserData(newUser));
return newUser;
}
);
export const updateUserShortcuts = createAsyncThunk(
'user/updateShortucts',
async (shortcuts, { dispatch, getState }) => {
const { user } = getState();
const newUser = {
...user,
data: {
...user.data,
shortcuts,
},
};
dispatch(updateUserData(newUser));
return newUser;
}
);
export const logoutUser = () => async (dispatch, getState) => {
const { user } = getState();
if (!user.role || user.role.length === 0) {
// is guest
return null;
}
history.push({
pathname: '/',
});
dispatch(setInitialSettings());
return dispatch(userLoggedOut());
};
export const updateUserData = (user) => async (dispatch, getState) => {
if (!user.role || user.role.length === 0) {
// is guest
return;
}
jwtService
.updateUserData(user)
.then(() => {
dispatch(showMessage({ message: 'User data saved with api' }));
})
.catch((error) => {
dispatch(showMessage({ message: error.message }));
});
};
const initialState = {
role: [], // guest
data: {
displayName: 'John Doe',
photoURL: 'assets/images/avatars/brian-hughes.jpg',
email: 'johndoe@withinpixels.com',
shortcuts: ['apps.calendar', 'apps.mailbox', 'apps.contacts', 'apps.tasks'],
},
};
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
userLoggedOut: (state, action) => initialState,
},
extraReducers: {
[updateUserSettings.fulfilled]: (state, action) => action.payload,
[updateUserShortcuts.fulfilled]: (state, action) => action.payload,
[setUser.fulfilled]: (state, action) => action.payload,
},
});
export const { userLoggedOut } = userSlice.actions;
export const selectUser = ({ user }) => user;
export const selectUserShortcuts = ({ user }) => user.data.shortcuts;
export default userSlice.reducer;

View File

@@ -0,0 +1,9 @@
import { injectReducer } from 'app/store/index';
const withReducer = (key, reducer) => (WrappedComponent) => {
injectReducer(key, reducer);
return (props) => <WrappedComponent {...props} />;
};
export default withReducer;