commit
4047a7ec23
378 changed files with 29334 additions and 0 deletions
87
redux/actions/app.js
Normal file
87
redux/actions/app.js
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { getItem } from 'lib/web';
|
||||
import {
|
||||
DEFAULT_LOCALE,
|
||||
DEFAULT_THEME,
|
||||
LOCALE_CONFIG,
|
||||
THEME_CONFIG,
|
||||
VERSION_CHECK,
|
||||
} from 'lib/constants';
|
||||
import semver from 'semver';
|
||||
|
||||
const app = createSlice({
|
||||
name: 'app',
|
||||
initialState: {
|
||||
locale: getItem(LOCALE_CONFIG) || DEFAULT_LOCALE,
|
||||
theme: getItem(THEME_CONFIG) || DEFAULT_THEME,
|
||||
versions: {
|
||||
current: process.env.VERSION,
|
||||
latest: null,
|
||||
hasUpdate: false,
|
||||
},
|
||||
shareToken: null,
|
||||
},
|
||||
reducers: {
|
||||
setLocale(state, action) {
|
||||
state.locale = action.payload;
|
||||
return state;
|
||||
},
|
||||
setTheme(state, action) {
|
||||
state.theme = action.payload;
|
||||
return state;
|
||||
},
|
||||
setVersions(state, action) {
|
||||
state.versions = action.payload;
|
||||
return state;
|
||||
},
|
||||
setShareToken(state, action) {
|
||||
state.shareToken = action.payload;
|
||||
return state;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { setLocale, setTheme, setVersions, setShareToken } = app.actions;
|
||||
|
||||
export default app.reducer;
|
||||
|
||||
export function checkVersion() {
|
||||
return async (dispatch, getState) => {
|
||||
const {
|
||||
app: {
|
||||
versions: { current },
|
||||
},
|
||||
} = getState();
|
||||
|
||||
const data = await fetch('https://api.github.com/repos/mikecao/umami/releases/latest', {
|
||||
method: 'get',
|
||||
headers: {
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
},
|
||||
}).then(res => {
|
||||
if (res.ok) {
|
||||
return res.json();
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { tag_name } = data;
|
||||
|
||||
const latest = tag_name.startsWith('v') ? tag_name.slice(1) : tag_name;
|
||||
const lastCheck = getItem(VERSION_CHECK);
|
||||
const hasUpdate = latest && semver.gt(latest, current) && lastCheck?.version !== latest;
|
||||
|
||||
return dispatch(
|
||||
setVersions({
|
||||
current,
|
||||
latest,
|
||||
hasUpdate,
|
||||
}),
|
||||
);
|
||||
};
|
||||
}
|
||||
17
redux/actions/queries.js
Normal file
17
redux/actions/queries.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
const queries = createSlice({
|
||||
name: 'queries',
|
||||
initialState: {},
|
||||
reducers: {
|
||||
updateQuery(state, action) {
|
||||
const { url, ...data } = action.payload;
|
||||
state[url] = data;
|
||||
return state;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { updateQuery } = queries.actions;
|
||||
|
||||
export default queries.reducer;
|
||||
16
redux/actions/user.js
Normal file
16
redux/actions/user.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
const user = createSlice({
|
||||
name: 'user',
|
||||
initialState: null,
|
||||
reducers: {
|
||||
updateUser(state, action) {
|
||||
state = action.payload;
|
||||
return state;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { updateUser } = user.actions;
|
||||
|
||||
export default user.reducer;
|
||||
29
redux/actions/websites.js
Normal file
29
redux/actions/websites.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
const websites = createSlice({
|
||||
name: 'websites',
|
||||
initialState: {},
|
||||
reducers: {
|
||||
updateWebsites(state, action) {
|
||||
state = action.payload;
|
||||
return state;
|
||||
},
|
||||
updateWebsite(state, action) {
|
||||
const { websiteId, ...data } = action.payload;
|
||||
state[websiteId] = data;
|
||||
return state;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { updateWebsites, updateWebsite } = websites.actions;
|
||||
|
||||
export default websites.reducer;
|
||||
|
||||
export function setDateRange(websiteId, dateRange) {
|
||||
return dispatch => {
|
||||
return dispatch(
|
||||
updateWebsite({ websiteId, dateRange: { ...dateRange, modified: Date.now() } }),
|
||||
);
|
||||
};
|
||||
}
|
||||
7
redux/reducers.js
Normal file
7
redux/reducers.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { combineReducers } from 'redux';
|
||||
import app from './actions/app';
|
||||
import user from './actions/user';
|
||||
import websites from './actions/websites';
|
||||
import queries from './actions/queries';
|
||||
|
||||
export default combineReducers({ app, user, websites, queries });
|
||||
40
redux/store.js
Normal file
40
redux/store.js
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import { useMemo } from 'react';
|
||||
import { configureStore } from '@reduxjs/toolkit';
|
||||
import thunk from 'redux-thunk';
|
||||
import rootReducer from './reducers';
|
||||
|
||||
let store;
|
||||
|
||||
export function getStore(preloadedState) {
|
||||
return configureStore({
|
||||
reducer: rootReducer,
|
||||
middleware: [thunk],
|
||||
preloadedState,
|
||||
});
|
||||
}
|
||||
|
||||
export const initializeStore = preloadedState => {
|
||||
let _store = store ?? getStore(preloadedState);
|
||||
|
||||
// After navigating to a page with an initial Redux state, merge that state
|
||||
// with the current state in the store, and create a new store
|
||||
if (preloadedState && store) {
|
||||
_store = getStore({
|
||||
...store.getState(),
|
||||
...preloadedState,
|
||||
});
|
||||
// Reset the current store
|
||||
store = undefined;
|
||||
}
|
||||
|
||||
// For SSG and SSR always create a new store
|
||||
if (typeof window === 'undefined') return _store;
|
||||
// Create the store once in the client
|
||||
if (!store) store = _store;
|
||||
|
||||
return _store;
|
||||
};
|
||||
|
||||
export function useStore(initialState) {
|
||||
return useMemo(() => initializeStore(initialState), [initialState]);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue