Initial commit

Created from https://vercel.com/new
This commit is contained in:
WaylonWalker 2021-12-08 18:25:42 +00:00
commit 4047a7ec23
378 changed files with 29334 additions and 0 deletions

87
redux/actions/app.js Normal file
View 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
View 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
View 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
View 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
View 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
View 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]);
}