mirror of
https://git.sr.ht/~ashkeel/strimertul
synced 2024-09-18 01:50:50 +00:00
Update frontend with new deps and linting
This commit is contained in:
parent
05cda342fd
commit
94d57b9a70
32 changed files with 6944 additions and 2034 deletions
1
driver.interface.go
Normal file
1
driver.interface.go
Normal file
|
@ -0,0 +1 @@
|
|||
package main
|
|
@ -1,23 +1,39 @@
|
|||
module.exports = {
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['@typescript-eslint', 'import'],
|
||||
extends: ['airbnb-base', 'plugin:@typescript-eslint/recommended', 'prettier'],
|
||||
extends: [
|
||||
'airbnb-base',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:@typescript-eslint/recommended-requiring-type-checking',
|
||||
'prettier',
|
||||
],
|
||||
root: true,
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: ['./tsconfig.json'],
|
||||
extraFileExtensions: ['.cjs'],
|
||||
},
|
||||
rules: {
|
||||
'no-console': 0,
|
||||
'import/extensions': 0,
|
||||
'no-use-before-define': 'off',
|
||||
'no-shadow': 'off',
|
||||
'no-unused-vars': 'off',
|
||||
'no-void': ['error', { allowAsStatement: true }],
|
||||
'@typescript-eslint/no-use-before-define': ['error'],
|
||||
'@typescript-eslint/no-shadow': ['error'],
|
||||
'default-case': 'off',
|
||||
'consistent-return': 'off',
|
||||
'@typescript-eslint/no-unused-vars': ['warn', { varsIgnorePattern: '^_' }],
|
||||
'@typescript-eslint/no-unsafe-return': ['error'],
|
||||
'@typescript-eslint/switch-exhaustiveness-check': ['error'],
|
||||
'@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
|
||||
},
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
node: {
|
||||
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
||||
moduleDirectory: ['node_modules', 'src/'],
|
||||
},
|
||||
typescript: {},
|
||||
},
|
||||
|
@ -25,5 +41,5 @@ module.exports = {
|
|||
env: {
|
||||
browser: true,
|
||||
},
|
||||
ignorePatterns: ['OLD/*'],
|
||||
ignorePatterns: ['OLD/*', 'wailsjs/*', 'dist/*'],
|
||||
};
|
8308
frontend/package-lock.json
generated
8308
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -4,39 +4,39 @@
|
|||
"type": "module",
|
||||
"dependencies": {
|
||||
"@billjs/event-emitter": "^1.0.3",
|
||||
"@fontsource/space-mono": "^4.5.0",
|
||||
"@fontsource/space-mono": "^4.5.10",
|
||||
"@radix-ui/colors": "^0.1.8",
|
||||
"@radix-ui/react-alert-dialog": "^0.1.5",
|
||||
"@radix-ui/react-checkbox": "^0.1.4",
|
||||
"@radix-ui/react-dialog": "^0.1.5",
|
||||
"@radix-ui/react-icons": "^1.0.3",
|
||||
"@radix-ui/react-label": "^0.1.3",
|
||||
"@radix-ui/react-tabs": "^0.1.4",
|
||||
"@radix-ui/react-toolbar": "^0.1.4",
|
||||
"@reduxjs/toolkit": "^1.5.1",
|
||||
"@stitches/react": "^1.2.6",
|
||||
"@strimertul/kilovolt-client": "^6.2.0",
|
||||
"@types/node": "^15.0.2",
|
||||
"@types/react": "^17.0.5",
|
||||
"@types/react-dom": "^17.0.4",
|
||||
"@vitejs/plugin-react": "^1.0.9",
|
||||
"i18next": "^20.6.1",
|
||||
"@radix-ui/react-alert-dialog": "^1.0.2",
|
||||
"@radix-ui/react-checkbox": "^1.0.1",
|
||||
"@radix-ui/react-dialog": "^1.0.2",
|
||||
"@radix-ui/react-icons": "^1.1.1",
|
||||
"@radix-ui/react-label": "^2.0.0",
|
||||
"@radix-ui/react-tabs": "^1.0.1",
|
||||
"@radix-ui/react-toolbar": "^1.0.1",
|
||||
"@redux-devtools/extension": "^3.2.3",
|
||||
"@reduxjs/toolkit": "^1.9.0",
|
||||
"@stitches/react": "^1.2.8",
|
||||
"@strimertul/kilovolt-client": "^6.4.0",
|
||||
"@types/node": "^18.11.9",
|
||||
"@types/react": "^18.0.25",
|
||||
"@types/react-dom": "^18.0.9",
|
||||
"@vitejs/plugin-react": "^2.2.0",
|
||||
"i18next": "^22.0.6",
|
||||
"inter-ui": "^3.19.3",
|
||||
"normalize.css": "^8.0.1",
|
||||
"overlayscrollbars": "^1.13.1",
|
||||
"overlayscrollbars-react": "^0.2.3",
|
||||
"postcss-import": "^14.0.2",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-i18next": "^11.12.0",
|
||||
"react-redux": "^7.2.4",
|
||||
"react-router-dom": "^6.1.1",
|
||||
"react-toastify": "^8.1.0",
|
||||
"redux-devtools-extension": "^2.13.9",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"sass": "^1.32.12",
|
||||
"typescript": "^4.2.4",
|
||||
"vite": "^2.6.14"
|
||||
"overlayscrollbars": "^2.0.1",
|
||||
"overlayscrollbars-react": "^0.5.0",
|
||||
"postcss-import": "^15.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-i18next": "^12.0.0",
|
||||
"react-redux": "^8.0.5",
|
||||
"react-router-dom": "^6.4.3",
|
||||
"react-toastify": "^9.1.1",
|
||||
"redux-thunk": "^2.4.2",
|
||||
"sass": "^1.56.1",
|
||||
"typescript": "^4.9.3",
|
||||
"vite": "^3.2.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
|
@ -47,15 +47,15 @@
|
|||
"last 1 Chrome version"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^4.23.0",
|
||||
"@typescript-eslint/parser": "^4.23.0",
|
||||
"eslint": "^7.26.0",
|
||||
"eslint-config-airbnb-base": "^14.2.1",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-import-resolver-typescript": "^2.4.0",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-prettier": "^3.4.0",
|
||||
"prettier": "^2.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.43.0",
|
||||
"@typescript-eslint/parser": "^5.43.0",
|
||||
"eslint": "^8.27.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-import-resolver-typescript": "^3.5.2",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"prettier": "^2.7.1",
|
||||
"rimraf": "^3.0.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { HashRouter } from 'react-router-dom';
|
||||
|
||||
import 'inter-ui/inter.css';
|
||||
import '@fontsource/space-mono/index.css';
|
||||
import 'normalize.css/normalize.css';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
import 'overlayscrollbars/css/OverlayScrollbars.css';
|
||||
import 'overlayscrollbars/overlayscrollbars.css';
|
||||
|
||||
import './locale/setup';
|
||||
|
||||
|
@ -19,9 +19,9 @@ globalStyles();
|
|||
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<BrowserRouter>
|
||||
<HashRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</HashRouter>
|
||||
</Provider>,
|
||||
document.getElementById('main'),
|
||||
);
|
||||
|
|
|
@ -1,13 +1,22 @@
|
|||
import { ActionCreatorWithOptionalPayload, AsyncThunk } from '@reduxjs/toolkit';
|
||||
import {
|
||||
ActionCreatorWithOptionalPayload,
|
||||
AsyncThunk,
|
||||
Draft,
|
||||
} from '@reduxjs/toolkit';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useSelector } from 'react-redux';
|
||||
import {
|
||||
KilovoltMessage,
|
||||
SubscriptionHandler,
|
||||
} from '@strimertul/kilovolt-client';
|
||||
import { RootState } from '../store';
|
||||
import { RootState, useAppDispatch } from '../store';
|
||||
import apiReducer, { getUserPoints } from '../store/api/reducer';
|
||||
import { APIState, LoyaltyStorage, RequestStatus } from '../store/api/types';
|
||||
import {
|
||||
APIState,
|
||||
LoyaltyPointsEntry,
|
||||
LoyaltyStorage,
|
||||
RequestStatus,
|
||||
} from '../store/api/types';
|
||||
|
||||
interface LoadStatus {
|
||||
load: RequestStatus;
|
||||
|
@ -20,9 +29,9 @@ export function useLiveKeyRaw(key: string) {
|
|||
|
||||
useEffect(() => {
|
||||
const subscriber: SubscriptionHandler = (v) => setData(v);
|
||||
client.subscribeKey(key, subscriber);
|
||||
void client.subscribeKey(key, subscriber);
|
||||
return () => {
|
||||
client.unsubscribeKey(key, subscriber);
|
||||
void client.unsubscribeKey(key, subscriber);
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
@ -31,7 +40,7 @@ export function useLiveKeyRaw(key: string) {
|
|||
|
||||
export function useLiveKey<T>(key: string): T {
|
||||
const data = useLiveKeyRaw(key);
|
||||
return data ? JSON.parse(data) : null;
|
||||
return data ? (JSON.parse(data) as T) : null;
|
||||
}
|
||||
|
||||
export function useModule<T>({
|
||||
|
@ -42,7 +51,7 @@ export function useModule<T>({
|
|||
asyncSetter,
|
||||
}: {
|
||||
key: string;
|
||||
selector: (state: APIState) => T;
|
||||
selector: (state: Draft<APIState>) => T;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
getter: AsyncThunk<T, void, {}>;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
|
@ -62,15 +71,15 @@ export function useModule<T>({
|
|||
const saveStatus = useSelector(
|
||||
(state: RootState) => state.api.requestStatus[`save-${key}`],
|
||||
);
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
useEffect(() => {
|
||||
dispatch(getter());
|
||||
const subscriber = (newValue) => {
|
||||
dispatch(asyncSetter(JSON.parse(newValue) as T));
|
||||
void dispatch(getter());
|
||||
const subscriber: SubscriptionHandler = (newValue) => {
|
||||
void dispatch(asyncSetter(JSON.parse(newValue) as T));
|
||||
};
|
||||
client.subscribeKey(key, subscriber);
|
||||
void client.subscribeKey(key, subscriber);
|
||||
return () => {
|
||||
client.unsubscribeKey(key, subscriber);
|
||||
void client.unsubscribeKey(key, subscriber);
|
||||
dispatch(
|
||||
apiReducer.actions.requestKeysRemoved([`save-${key}`, `load-${key}`]),
|
||||
);
|
||||
|
@ -93,7 +102,7 @@ export function useStatus(
|
|||
const [localStatus, setlocalStatus] = useState(status);
|
||||
const maxTime = Date.now() - interval;
|
||||
useEffect(() => {
|
||||
const remaining = status?.updated.getTime() - maxTime;
|
||||
const remaining = status ? status.updated.getTime() - maxTime : null;
|
||||
if (remaining) {
|
||||
setTimeout(() => {
|
||||
setlocalStatus(null);
|
||||
|
@ -109,17 +118,19 @@ export function useUserPoints(): LoyaltyStorage {
|
|||
const prefix = 'loyalty/points/';
|
||||
const client = useSelector((state: RootState) => state.api.client);
|
||||
const data = useSelector((state: RootState) => state.api.loyalty.users);
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
useEffect(() => {
|
||||
dispatch(getUserPoints());
|
||||
void dispatch(getUserPoints());
|
||||
const subscriber: SubscriptionHandler = (newValue, key) => {
|
||||
const user = key.substring(prefix.length);
|
||||
const entry = JSON.parse(newValue);
|
||||
dispatch(apiReducer.actions.loyaltyUserPointsChanged({ user, entry }));
|
||||
const entry = JSON.parse(newValue) as LoyaltyPointsEntry;
|
||||
void dispatch(
|
||||
apiReducer.actions.loyaltyUserPointsChanged({ user, entry }),
|
||||
);
|
||||
};
|
||||
client.subscribePrefix(prefix, subscriber);
|
||||
void client.subscribePrefix(prefix, subscriber);
|
||||
return () => {
|
||||
client.unsubscribePrefix(prefix, subscriber);
|
||||
void client.unsubscribePrefix(prefix, subscriber);
|
||||
};
|
||||
}, []);
|
||||
return data;
|
||||
|
|
|
@ -2,6 +2,15 @@ export interface StulbeOptions {
|
|||
controller: AbortController;
|
||||
}
|
||||
|
||||
type stulbeAuthResult =
|
||||
| {
|
||||
ok: true;
|
||||
token: string;
|
||||
}
|
||||
| {
|
||||
error: string;
|
||||
};
|
||||
|
||||
export default class Stulbe {
|
||||
private token: string;
|
||||
|
||||
|
@ -12,7 +21,7 @@ export default class Stulbe {
|
|||
) {}
|
||||
|
||||
public async auth(user: string, key: string): Promise<boolean> {
|
||||
const res = await (
|
||||
const res: stulbeAuthResult = (await (
|
||||
await fetch(`${this.endpoint}/api/auth`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
@ -24,23 +33,23 @@ export default class Stulbe {
|
|||
}),
|
||||
signal: this.options?.controller.signal,
|
||||
})
|
||||
).json();
|
||||
if (!res.ok) {
|
||||
).json()) as stulbeAuthResult;
|
||||
if ('error' in res) {
|
||||
throw new Error(res.error);
|
||||
}
|
||||
this.token = res.token;
|
||||
return res.ok;
|
||||
}
|
||||
|
||||
public async makeRequest<T>(
|
||||
public async makeRequest<T, B extends BodyInit | URLSearchParams>(
|
||||
method: string,
|
||||
path: string,
|
||||
body?: any,
|
||||
body?: B,
|
||||
): Promise<T> {
|
||||
if (!this.token) {
|
||||
throw new Error('not authenticated');
|
||||
}
|
||||
const res = await (
|
||||
const res = (await (
|
||||
await fetch(`${this.endpoint}/${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
|
@ -50,7 +59,7 @@ export default class Stulbe {
|
|||
body,
|
||||
signal: this.options?.controller.signal,
|
||||
})
|
||||
).json();
|
||||
).json()) as T;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -193,7 +193,8 @@
|
|||
"no-redeems": "No pending redeems",
|
||||
"no-users": "No viewers found",
|
||||
"refund": "Refund",
|
||||
"accept": "Accept"
|
||||
"accept": "Accept",
|
||||
"hardcoded": "My Random hardcoded text"
|
||||
},
|
||||
"debug": {
|
||||
"dismiss-warning": "I am not afraid! ...well ok maybe a little",
|
||||
|
|
|
@ -3,7 +3,7 @@ import { initReactI18next } from 'react-i18next';
|
|||
|
||||
import en from './en/translation.json';
|
||||
|
||||
i18n.use(initReactI18next).init({
|
||||
void i18n.use(initReactI18next).init({
|
||||
resources: {
|
||||
en: {
|
||||
translation: en,
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
/* eslint-disable no-param-reassign */
|
||||
import {
|
||||
AnyAction,
|
||||
AsyncThunk,
|
||||
CaseReducer,
|
||||
createAction,
|
||||
createAsyncThunk,
|
||||
createSlice,
|
||||
Dispatch,
|
||||
PayloadAction,
|
||||
} from '@reduxjs/toolkit';
|
||||
import KilovoltWS from '@strimertul/kilovolt-client';
|
||||
import { kvError } from '@strimertul/kilovolt-client/lib/messages';
|
||||
import type { kvError } from '@strimertul/kilovolt-client/types/messages';
|
||||
import {
|
||||
APIState,
|
||||
ConnectionStatus,
|
||||
|
@ -17,8 +19,13 @@ import {
|
|||
LoyaltyStorage,
|
||||
} from './types';
|
||||
|
||||
interface AppThunkAPI {
|
||||
dispatch: Dispatch;
|
||||
getState: () => unknown;
|
||||
}
|
||||
|
||||
function makeGetterThunk<T>(key: string) {
|
||||
return async (_: void, { getState }) => {
|
||||
return async (_: void, { getState }: AppThunkAPI) => {
|
||||
const { api } = getState() as { api: APIState };
|
||||
return api.client.getJSON<T>(key);
|
||||
};
|
||||
|
@ -29,13 +36,15 @@ function makeSetterThunk<T>(
|
|||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
getter: AsyncThunk<T, void, {}>,
|
||||
) {
|
||||
return async (data: T, { getState, dispatch }) => {
|
||||
return async (data: T, { getState, dispatch }: AppThunkAPI) => {
|
||||
const { api } = getState() as { api: APIState };
|
||||
const result = await api.client.putJSON(key, data);
|
||||
if ('ok' in result) {
|
||||
if (result.ok) {
|
||||
// Re-load value from KV
|
||||
dispatch(getter());
|
||||
// Need to do type fuckery to avoid cyclic redundancy
|
||||
// (unless there's a better way that I'm missing)
|
||||
void dispatch(getter() as unknown as AnyAction);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
@ -84,10 +93,10 @@ export const createWSClient = createAsyncThunk(
|
|||
async (options: { address: string; password?: string }, { dispatch }) => {
|
||||
const client = new KilovoltWS(options.address, options.password);
|
||||
client.on('error', (err) => {
|
||||
dispatch(kvErrorReceived(err.data));
|
||||
void dispatch(kvErrorReceived(err.data as kvError));
|
||||
});
|
||||
await client.wait();
|
||||
dispatch(setupClientReconnect(client));
|
||||
await dispatch(setupClientReconnect(client));
|
||||
return client;
|
||||
},
|
||||
);
|
||||
|
@ -99,9 +108,9 @@ export const getUserPoints = createAsyncThunk(
|
|||
const keys = await api.client.getKeysByPrefix(loyaltyPointsPrefix);
|
||||
const userpoints: LoyaltyStorage = {};
|
||||
Object.entries(keys).forEach(([k, v]) => {
|
||||
userpoints[k.substr(loyaltyPointsPrefix.length)] = JSON.parse(
|
||||
v as string,
|
||||
);
|
||||
userpoints[k.substring(loyaltyPointsPrefix.length)] = JSON.parse(
|
||||
v,
|
||||
) as LoyaltyPointsEntry;
|
||||
});
|
||||
return userpoints;
|
||||
},
|
||||
|
@ -131,6 +140,7 @@ export const modules = {
|
|||
'http/config',
|
||||
(state) => state.moduleConfigs?.httpConfig,
|
||||
(state, { payload }) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
state.moduleConfigs.httpConfig = payload;
|
||||
},
|
||||
),
|
||||
|
@ -138,6 +148,7 @@ export const modules = {
|
|||
'twitch/config',
|
||||
(state) => state.moduleConfigs?.twitchConfig,
|
||||
(state, { payload }) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
state.moduleConfigs.twitchConfig = payload;
|
||||
},
|
||||
),
|
||||
|
@ -145,6 +156,7 @@ export const modules = {
|
|||
'twitch/bot-config',
|
||||
(state) => state.moduleConfigs?.twitchBotConfig,
|
||||
(state, { payload }) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
state.moduleConfigs.twitchBotConfig = payload;
|
||||
},
|
||||
),
|
||||
|
@ -152,6 +164,7 @@ export const modules = {
|
|||
'twitch/bot-custom-commands',
|
||||
(state) => state.twitchBot?.commands,
|
||||
(state, { payload }) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
state.twitchBot.commands = payload;
|
||||
},
|
||||
),
|
||||
|
@ -159,6 +172,7 @@ export const modules = {
|
|||
'twitch/bot-modules/timers/config',
|
||||
(state) => state.twitchBot?.timers,
|
||||
(state, { payload }) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
state.twitchBot.timers = payload;
|
||||
},
|
||||
),
|
||||
|
@ -166,6 +180,7 @@ export const modules = {
|
|||
'twitch/bot-modules/alerts/config',
|
||||
(state) => state.twitchBot?.alerts,
|
||||
(state, { payload }) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
state.twitchBot.alerts = payload;
|
||||
},
|
||||
),
|
||||
|
@ -173,6 +188,7 @@ export const modules = {
|
|||
'stulbe/config',
|
||||
(state) => state.moduleConfigs?.stulbeConfig,
|
||||
(state, { payload }) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
state.moduleConfigs.stulbeConfig = payload;
|
||||
},
|
||||
),
|
||||
|
@ -180,6 +196,7 @@ export const modules = {
|
|||
'loyalty/config',
|
||||
(state) => state.moduleConfigs?.loyaltyConfig,
|
||||
(state, { payload }) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
state.moduleConfigs.loyaltyConfig = payload;
|
||||
},
|
||||
),
|
||||
|
@ -187,6 +204,7 @@ export const modules = {
|
|||
loyaltyRewardsKey,
|
||||
(state) => state.loyalty.rewards,
|
||||
(state, { payload }) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
state.loyalty.rewards = payload;
|
||||
},
|
||||
),
|
||||
|
@ -194,6 +212,7 @@ export const modules = {
|
|||
'loyalty/goals',
|
||||
(state) => state.loyalty.goals,
|
||||
(state, { payload }) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
state.loyalty.goals = payload;
|
||||
},
|
||||
),
|
||||
|
@ -201,6 +220,7 @@ export const modules = {
|
|||
'loyalty/redeem-queue',
|
||||
(state) => state.loyalty.redeemQueue,
|
||||
(state, { payload }) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
state.loyalty.redeemQueue = payload;
|
||||
},
|
||||
),
|
||||
|
@ -344,9 +364,9 @@ const apiReducer = createSlice({
|
|||
|
||||
setupClientReconnect = createAsyncThunk(
|
||||
'api/setupClientReconnect',
|
||||
async (client: KilovoltWS, { dispatch }) => {
|
||||
(client: KilovoltWS, { dispatch }) => {
|
||||
client.on('close', () => {
|
||||
setTimeout(async () => {
|
||||
setTimeout(() => {
|
||||
console.info('Attempting reconnection');
|
||||
client.reconnect();
|
||||
}, 5000);
|
||||
|
@ -366,7 +386,7 @@ setupClientReconnect = createAsyncThunk(
|
|||
|
||||
kvErrorReceived = createAsyncThunk(
|
||||
'api/kvErrorReceived',
|
||||
async (error: kvError, { dispatch }) => {
|
||||
(error: kvError, { dispatch }) => {
|
||||
switch (error.error) {
|
||||
case 'authentication required':
|
||||
case 'authentication failed':
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable camelcase */
|
||||
|
||||
import KilovoltWS from '@strimertul/kilovolt-client';
|
||||
import { kvError } from '@strimertul/kilovolt-client/lib/messages';
|
||||
import type { kvError } from '@strimertul/kilovolt-client/types/messages';
|
||||
|
||||
interface HTTPConfig {
|
||||
bind: string;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { configureStore } from '@reduxjs/toolkit';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import thunkMiddleware from 'redux-thunk';
|
||||
import apiReducer from './api/reducer';
|
||||
|
||||
|
@ -14,5 +15,7 @@ const store = configureStore({
|
|||
});
|
||||
|
||||
export type RootState = ReturnType<typeof store.getState>;
|
||||
export type AppDispatch = typeof store.dispatch;
|
||||
export const useAppDispatch: () => AppDispatch = useDispatch;
|
||||
|
||||
export default store;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useSelector } from 'react-redux';
|
||||
import {
|
||||
ChatBubbleIcon,
|
||||
DashboardIcon,
|
||||
|
@ -17,7 +17,7 @@ import { ToastContainer } from 'react-toastify';
|
|||
import Dashboard from './pages/Dashboard';
|
||||
import Sidebar, { RouteSection } from './components/Sidebar';
|
||||
import ServerSettingsPage from './pages/ServerSettings';
|
||||
import { RootState } from '../store';
|
||||
import { RootState, useAppDispatch } from '../store';
|
||||
import { createWSClient } from '../store/api/reducer';
|
||||
import { ConnectionStatus } from '../store/api/types';
|
||||
import { styled } from './theme';
|
||||
|
@ -50,7 +50,7 @@ const Spinner = styled('img', {
|
|||
function Loading() {
|
||||
return (
|
||||
<LoadingDiv>
|
||||
<Spinner src={spinner} alt="Loading..." />
|
||||
<Spinner src={spinner as string} alt="Loading..." />
|
||||
</LoadingDiv>
|
||||
);
|
||||
}
|
||||
|
@ -152,17 +152,17 @@ export default function App(): JSX.Element {
|
|||
const connected = useSelector(
|
||||
(state: RootState) => state.api.connectionStatus,
|
||||
);
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
if (!client) {
|
||||
dispatch(
|
||||
void dispatch(
|
||||
createWSClient({
|
||||
address:
|
||||
process.env.NODE_ENV === 'development'
|
||||
? 'ws://localhost:4337/ws'
|
||||
: `ws://${window.location.host}/ws`,
|
||||
password: localStorage.password,
|
||||
password: localStorage.password as string,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -75,6 +75,7 @@ export function DataTable<T>({
|
|||
case 'desc':
|
||||
return -result;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -155,8 +155,9 @@ export default function Sidebar({
|
|||
const matchApp = useMatch({ path: resolved.pathname, end: true });
|
||||
const client = useSelector((state: RootState) => state.api.client);
|
||||
const [version, setVersion] = useState<string>(null);
|
||||
const [lastVersion, setLastVersion] =
|
||||
useState<{ url: string; name: string }>(null);
|
||||
const [lastVersion, setLastVersion] = useState<{ url: string; name: string }>(
|
||||
null,
|
||||
);
|
||||
const dev = version && version.startsWith('v0.0.0');
|
||||
|
||||
async function fetchVersion() {
|
||||
|
@ -174,7 +175,7 @@ export default function Sidebar({
|
|||
},
|
||||
},
|
||||
);
|
||||
const data = await req.json();
|
||||
const data = (await req.json()) as { html_url: string; name: string };
|
||||
setLastVersion({
|
||||
url: data.html_url,
|
||||
name: data.name,
|
||||
|
@ -186,12 +187,12 @@ export default function Sidebar({
|
|||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchLastVersion();
|
||||
void fetchLastVersion();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (client) {
|
||||
fetchVersion();
|
||||
void fetchVersion();
|
||||
}
|
||||
}, [client]);
|
||||
|
||||
|
@ -205,7 +206,7 @@ export default function Sidebar({
|
|||
<AppLink to={'/about'} status={matchApp ? 'active' : 'default'}>
|
||||
<AppName>
|
||||
<img
|
||||
src={logo}
|
||||
src={logo as string}
|
||||
style={{ height: '28px', marginBottom: '-2px' }}
|
||||
/>
|
||||
{APPNAME}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { toast } from 'react-toastify';
|
||||
import { ExternalLinkIcon } from '@radix-ui/react-icons';
|
||||
import { useModule, useStatus } from '../../lib/react-utils';
|
||||
|
@ -25,7 +25,7 @@ import {
|
|||
TextBlock,
|
||||
} from '../theme';
|
||||
import eventsubTests from '../../data/eventsub-tests';
|
||||
import { RootState } from '../../store';
|
||||
import { RootState, useAppDispatch } from '../../store';
|
||||
|
||||
interface UserData {
|
||||
id: string;
|
||||
|
@ -41,36 +41,6 @@ interface SyncError {
|
|||
error: string;
|
||||
}
|
||||
|
||||
const eventSubTestFn = {
|
||||
'channel.update': (send) => {
|
||||
send(eventsubTests['channel.update']);
|
||||
},
|
||||
'channel.follow': (send) => {
|
||||
send(eventsubTests['channel.follow']);
|
||||
},
|
||||
'channel.subscribe': (send) => {
|
||||
send(eventsubTests['channel.subscribe']);
|
||||
},
|
||||
'channel.subscription.gift': (send) => {
|
||||
send(eventsubTests['channel.subscription.gift']);
|
||||
setTimeout(() => {
|
||||
send(eventsubTests['channel.subscribe']);
|
||||
}, 2000);
|
||||
},
|
||||
'channel.subscription.message': (send) => {
|
||||
send(eventsubTests['channel.subscribe']);
|
||||
setTimeout(() => {
|
||||
send(eventsubTests['channel.subscription.message']);
|
||||
}, 2000);
|
||||
},
|
||||
'channel.cheer': (send) => {
|
||||
send(eventsubTests['channel.cheer']);
|
||||
},
|
||||
'channel.raid': (send) => {
|
||||
send(eventsubTests['channel.raid']);
|
||||
},
|
||||
};
|
||||
|
||||
const TwitchUser = styled('div', {
|
||||
display: 'flex',
|
||||
gap: '0.8rem',
|
||||
|
@ -84,6 +54,11 @@ const TwitchPic = styled('img', {
|
|||
});
|
||||
const TwitchName = styled('p', { fontWeight: 'bold' });
|
||||
|
||||
interface authChallengeRequest {
|
||||
// eslint-disable-next-line camelcase
|
||||
auth_url: string;
|
||||
}
|
||||
|
||||
function WebhookIntegration() {
|
||||
const { t } = useTranslation();
|
||||
const [stulbeConfig] = useModule(modules.stulbeConfig);
|
||||
|
@ -93,21 +68,21 @@ function WebhookIntegration() {
|
|||
|
||||
const getUserInfo = async () => {
|
||||
try {
|
||||
const res = (await client.makeRequest(
|
||||
const res = await client.makeRequest<UserData, null>(
|
||||
'GET',
|
||||
'api/twitch/user',
|
||||
)) as UserData;
|
||||
);
|
||||
setUserStatus(res);
|
||||
} catch (e) {
|
||||
setUserStatus({ ok: false, error: e.message });
|
||||
setUserStatus({ ok: false, error: (e as Error).message });
|
||||
}
|
||||
};
|
||||
|
||||
const startAuthFlow = async () => {
|
||||
const res = (await client.makeRequest('POST', 'api/twitch/authorize')) as {
|
||||
// eslint-disable-next-line camelcase
|
||||
auth_url: string;
|
||||
};
|
||||
const res = await client.makeRequest<authChallengeRequest, null>(
|
||||
'POST',
|
||||
'api/twitch/authorize',
|
||||
);
|
||||
const win = window.open(
|
||||
res.auth_url,
|
||||
'_blank',
|
||||
|
@ -118,28 +93,27 @@ function WebhookIntegration() {
|
|||
if (win.closed) {
|
||||
clearInterval(iv);
|
||||
setUserStatus(null);
|
||||
getUserInfo();
|
||||
void getUserInfo();
|
||||
}
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const sendFakeEvent = async (event: keyof typeof eventSubTestFn) => {
|
||||
eventSubTestFn[event]((data) => {
|
||||
kv.putJSON('stulbe/ev/webhook', {
|
||||
const sendFakeEvent = async (event: keyof typeof eventsubTests) => {
|
||||
const data = eventsubTests[event];
|
||||
await kv.putJSON('stulbe/ev/webhook', {
|
||||
...data,
|
||||
subscription: {
|
||||
...data.subscription,
|
||||
created_at: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line consistent-return
|
||||
useEffect(() => {
|
||||
if (client) {
|
||||
// Get user info
|
||||
getUserInfo();
|
||||
void getUserInfo();
|
||||
} else if (
|
||||
stulbeConfig &&
|
||||
stulbeConfig.enabled &&
|
||||
|
@ -153,7 +127,7 @@ function WebhookIntegration() {
|
|||
await stulbeClient.auth(stulbeConfig.username, stulbeConfig.auth_key);
|
||||
setClient(stulbeClient);
|
||||
};
|
||||
tryAuth();
|
||||
void tryAuth();
|
||||
}
|
||||
}, [stulbeConfig, client]);
|
||||
|
||||
|
@ -177,21 +151,32 @@ function WebhookIntegration() {
|
|||
</>
|
||||
);
|
||||
} else {
|
||||
userBlock = t('pages.stulbe.err-no-user');
|
||||
userBlock = <span>{t('pages.stulbe.err-no-user')}</span>;
|
||||
}
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<p>{t('pages.stulbe.auth-message')}</p>
|
||||
<Button variation="primary" onClick={startAuthFlow} disabled={!client}>
|
||||
<Button
|
||||
variation="primary"
|
||||
onClick={() => {
|
||||
void startAuthFlow();
|
||||
}}
|
||||
disabled={!client}
|
||||
>
|
||||
<ExternalLinkIcon /> {t('pages.stulbe.auth-button')}
|
||||
</Button>
|
||||
<SectionHeader>{t('pages.stulbe.current-status')}</SectionHeader>
|
||||
{userBlock}
|
||||
<SectionHeader>{t('pages.stulbe.sim-events')}</SectionHeader>
|
||||
<ButtonGroup>
|
||||
{Object.keys(eventSubTestFn).map((ev: keyof typeof eventsubTests) => (
|
||||
<Button key={ev} onClick={() => sendFakeEvent(ev)}>
|
||||
{Object.keys(eventsubTests).map((ev: keyof typeof eventsubTests) => (
|
||||
<Button
|
||||
key={ev}
|
||||
onClick={() => {
|
||||
void sendFakeEvent(ev);
|
||||
}}
|
||||
>
|
||||
{t(`pages.stulbe.sim.${ev}`, { defaultValue: ev })}
|
||||
</Button>
|
||||
))}
|
||||
|
@ -205,7 +190,7 @@ function BackendConfiguration() {
|
|||
modules.stulbeConfig,
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
const status = useStatus(loadStatus.save);
|
||||
const active = stulbeConfig?.enabled ?? false;
|
||||
const busy =
|
||||
|
@ -217,14 +202,14 @@ function BackendConfiguration() {
|
|||
await client.auth(stulbeConfig.username, stulbeConfig.auth_key);
|
||||
toast.success(t('pages.stulbe.test-success'));
|
||||
} catch (e) {
|
||||
toast.error(e.message);
|
||||
toast.error((e as Error).message);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={(ev) => {
|
||||
dispatch(setStulbeConfig(stulbeConfig));
|
||||
void dispatch(setStulbeConfig(stulbeConfig));
|
||||
ev.preventDefault();
|
||||
}}
|
||||
>
|
||||
|
@ -236,15 +221,15 @@ function BackendConfiguration() {
|
|||
placeholder={t('pages.stulbe.bind-placeholder')}
|
||||
value={stulbeConfig?.endpoint ?? ''}
|
||||
disabled={busy}
|
||||
onChange={(e) =>
|
||||
dispatch(
|
||||
onChange={(e) => {
|
||||
void dispatch(
|
||||
apiReducer.actions.stulbeConfigChanged({
|
||||
...stulbeConfig,
|
||||
enabled: e.target.value.length > 0,
|
||||
endpoint: e.target.value,
|
||||
}),
|
||||
)
|
||||
}
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
<Field size="fullWidth">
|
||||
|
@ -255,14 +240,14 @@ function BackendConfiguration() {
|
|||
value={stulbeConfig?.username ?? ''}
|
||||
required={true}
|
||||
disabled={!active || busy}
|
||||
onChange={(e) =>
|
||||
dispatch(
|
||||
onChange={(e) => {
|
||||
void dispatch(
|
||||
apiReducer.actions.stulbeConfigChanged({
|
||||
...stulbeConfig,
|
||||
username: e.target.value,
|
||||
}),
|
||||
)
|
||||
}
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
<Field size="fullWidth">
|
||||
|
@ -273,19 +258,25 @@ function BackendConfiguration() {
|
|||
value={stulbeConfig?.auth_key ?? ''}
|
||||
disabled={!active || busy}
|
||||
required={true}
|
||||
onChange={(e) =>
|
||||
dispatch(
|
||||
onChange={(e) => {
|
||||
void dispatch(
|
||||
apiReducer.actions.stulbeConfigChanged({
|
||||
...stulbeConfig,
|
||||
auth_key: e.target.value,
|
||||
}),
|
||||
)
|
||||
}
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
<ButtonGroup>
|
||||
<SaveButton status={status} />
|
||||
<Button type="button" disabled={!active || busy} onClick={() => test()}>
|
||||
<Button
|
||||
type="button"
|
||||
disabled={!active || busy}
|
||||
onClick={() => {
|
||||
void test();
|
||||
}}
|
||||
>
|
||||
{t('pages.stulbe.test-button')}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { PlusIcon } from '@radix-ui/react-icons';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useModule } from '../../lib/react-utils';
|
||||
import { useAppDispatch } from '../../store';
|
||||
import { modules } from '../../store/api/reducer';
|
||||
import {
|
||||
accessLevels,
|
||||
|
@ -213,7 +213,7 @@ function CommandDialog({
|
|||
...item,
|
||||
description,
|
||||
response,
|
||||
access_level: accessLevel as AccessLevelType,
|
||||
access_level: accessLevel,
|
||||
});
|
||||
}
|
||||
}}
|
||||
|
@ -290,14 +290,14 @@ export default function TwitchBotCommandsPage(): React.ReactElement {
|
|||
const [filter, setFilter] = useState('');
|
||||
const [activeDialog, setActiveDialog] = useState<DialogPrompt>(null);
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const filterLC = filter.toLowerCase();
|
||||
|
||||
const setCommand = (newName: string, data: TwitchBotCustomCommand): void => {
|
||||
switch (activeDialog.kind) {
|
||||
case 'new':
|
||||
dispatch(
|
||||
void dispatch(
|
||||
setCommands({
|
||||
...commands,
|
||||
[newName]: {
|
||||
|
@ -309,7 +309,7 @@ export default function TwitchBotCommandsPage(): React.ReactElement {
|
|||
break;
|
||||
case 'edit': {
|
||||
const oldName = activeDialog.name;
|
||||
dispatch(
|
||||
void dispatch(
|
||||
setCommands({
|
||||
...commands,
|
||||
[oldName]: undefined,
|
||||
|
@ -323,7 +323,7 @@ export default function TwitchBotCommandsPage(): React.ReactElement {
|
|||
};
|
||||
|
||||
const deleteCommand = (cmd: string): void => {
|
||||
dispatch(
|
||||
void dispatch(
|
||||
setCommands({
|
||||
...commands,
|
||||
[cmd]: undefined,
|
||||
|
@ -332,7 +332,7 @@ export default function TwitchBotCommandsPage(): React.ReactElement {
|
|||
};
|
||||
|
||||
const toggleCommand = (cmd: string): void => {
|
||||
dispatch(
|
||||
void dispatch(
|
||||
setCommands({
|
||||
...commands,
|
||||
[cmd]: {
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { PlusIcon } from '@radix-ui/react-icons';
|
||||
import { TFunction } from 'i18next';
|
||||
import React, { useState } from 'react';
|
||||
import { TFunction, useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useModule } from '../../lib/react-utils';
|
||||
import { useAppDispatch } from '../../store';
|
||||
import { modules } from '../../store/api/reducer';
|
||||
import { TwitchBotTimer } from '../../store/api/types';
|
||||
import AlertContent from '../components/AlertContent';
|
||||
|
@ -303,14 +304,14 @@ export default function TwitchBotTimersPage(): React.ReactElement {
|
|||
const [filter, setFilter] = useState('');
|
||||
const [activeDialog, setActiveDialog] = useState<DialogPrompt>(null);
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const filterLC = filter.toLowerCase();
|
||||
|
||||
const setTimer = (newName: string, data: TwitchBotTimer): void => {
|
||||
switch (activeDialog.kind) {
|
||||
case 'new':
|
||||
dispatch(
|
||||
void dispatch(
|
||||
setTimerConfig({
|
||||
...timerConfig,
|
||||
timers: {
|
||||
|
@ -325,7 +326,7 @@ export default function TwitchBotTimersPage(): React.ReactElement {
|
|||
break;
|
||||
case 'edit': {
|
||||
const oldName = activeDialog.name;
|
||||
dispatch(
|
||||
void dispatch(
|
||||
setTimerConfig({
|
||||
...timerConfig,
|
||||
timers: {
|
||||
|
@ -342,7 +343,7 @@ export default function TwitchBotTimersPage(): React.ReactElement {
|
|||
};
|
||||
|
||||
const deleteTimer = (cmd: string): void => {
|
||||
dispatch(
|
||||
void dispatch(
|
||||
setTimerConfig({
|
||||
...timerConfig,
|
||||
timers: {
|
||||
|
@ -354,7 +355,7 @@ export default function TwitchBotTimersPage(): React.ReactElement {
|
|||
};
|
||||
|
||||
const toggleTimer = (cmd: string): void => {
|
||||
dispatch(
|
||||
void dispatch(
|
||||
setTimerConfig({
|
||||
...timerConfig,
|
||||
timers: {
|
||||
|
@ -391,7 +392,7 @@ export default function TwitchBotTimersPage(): React.ReactElement {
|
|||
/>
|
||||
</FlexRow>
|
||||
<TimerList>
|
||||
{!!timerConfig?.timers ? (
|
||||
{timerConfig?.timers ? (
|
||||
Object.keys(timerConfig?.timers ?? {})
|
||||
?.filter((cmd) => cmd.toLowerCase().includes(filterLC))
|
||||
.sort()
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { CheckIcon } from '@radix-ui/react-icons';
|
||||
import { useModule, useStatus } from '../../lib/react-utils';
|
||||
import apiReducer, { modules } from '../../store/api/reducer';
|
||||
|
@ -21,10 +20,11 @@ import {
|
|||
TextBlock,
|
||||
} from '../theme';
|
||||
import SaveButton from '../components/utils/SaveButton';
|
||||
import { useAppDispatch } from '../../store';
|
||||
|
||||
export default function ChatAlertsPage(): React.ReactElement {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
const [alerts, setAlerts, loadStatus] = useModule(modules.twitchBotAlerts);
|
||||
const status = useStatus(loadStatus.save);
|
||||
|
||||
|
@ -32,7 +32,7 @@ export default function ChatAlertsPage(): React.ReactElement {
|
|||
<PageContainer>
|
||||
<form
|
||||
onSubmit={(ev) => {
|
||||
dispatch(setAlerts(alerts));
|
||||
void dispatch(setAlerts(alerts));
|
||||
ev.preventDefault();
|
||||
}}
|
||||
>
|
||||
|
@ -61,8 +61,8 @@ export default function ChatAlertsPage(): React.ReactElement {
|
|||
<FlexRow spacing={1} align="left">
|
||||
<Checkbox
|
||||
checked={alerts?.follow?.enabled ?? false}
|
||||
onCheckedChange={(ev) =>
|
||||
dispatch(
|
||||
onCheckedChange={(ev) => {
|
||||
void dispatch(
|
||||
apiReducer.actions.twitchBotAlertsChanged({
|
||||
...alerts,
|
||||
follow: {
|
||||
|
@ -70,8 +70,8 @@ export default function ChatAlertsPage(): React.ReactElement {
|
|||
enabled: !!ev,
|
||||
},
|
||||
}),
|
||||
)
|
||||
}
|
||||
);
|
||||
}}
|
||||
id="follow-enabled"
|
||||
>
|
||||
<CheckboxIndicator>
|
||||
|
@ -107,8 +107,8 @@ export default function ChatAlertsPage(): React.ReactElement {
|
|||
<FlexRow spacing={1} align="left">
|
||||
<Checkbox
|
||||
checked={alerts?.subscription?.enabled ?? false}
|
||||
onCheckedChange={(ev) =>
|
||||
dispatch(
|
||||
onCheckedChange={(ev) => {
|
||||
void dispatch(
|
||||
apiReducer.actions.twitchBotAlertsChanged({
|
||||
...alerts,
|
||||
subscription: {
|
||||
|
@ -116,8 +116,8 @@ export default function ChatAlertsPage(): React.ReactElement {
|
|||
enabled: !!ev,
|
||||
},
|
||||
}),
|
||||
)
|
||||
}
|
||||
);
|
||||
}}
|
||||
id="subscription-enabled"
|
||||
>
|
||||
<CheckboxIndicator>
|
||||
|
@ -138,7 +138,7 @@ export default function ChatAlertsPage(): React.ReactElement {
|
|||
disabled={!alerts?.subscription?.enabled ?? true}
|
||||
required={alerts?.subscription?.enabled ?? false}
|
||||
onChange={(messages) => {
|
||||
dispatch(
|
||||
void dispatch(
|
||||
apiReducer.actions.twitchBotAlertsChanged({
|
||||
...alerts,
|
||||
subscription: { ...alerts.subscription, messages },
|
||||
|
@ -154,8 +154,8 @@ export default function ChatAlertsPage(): React.ReactElement {
|
|||
<FlexRow spacing={1} align="left">
|
||||
<Checkbox
|
||||
checked={alerts?.gift_sub?.enabled ?? false}
|
||||
onCheckedChange={(ev) =>
|
||||
dispatch(
|
||||
onCheckedChange={(ev) => {
|
||||
void dispatch(
|
||||
apiReducer.actions.twitchBotAlertsChanged({
|
||||
...alerts,
|
||||
gift_sub: {
|
||||
|
@ -163,8 +163,8 @@ export default function ChatAlertsPage(): React.ReactElement {
|
|||
enabled: !!ev,
|
||||
},
|
||||
}),
|
||||
)
|
||||
}
|
||||
);
|
||||
}}
|
||||
id="gift_sub-enabled"
|
||||
>
|
||||
<CheckboxIndicator>
|
||||
|
@ -185,7 +185,7 @@ export default function ChatAlertsPage(): React.ReactElement {
|
|||
disabled={!alerts?.gift_sub?.enabled ?? true}
|
||||
required={alerts?.gift_sub?.enabled ?? false}
|
||||
onChange={(messages) => {
|
||||
dispatch(
|
||||
void dispatch(
|
||||
apiReducer.actions.twitchBotAlertsChanged({
|
||||
...alerts,
|
||||
gift_sub: { ...alerts.gift_sub, messages },
|
||||
|
@ -201,8 +201,8 @@ export default function ChatAlertsPage(): React.ReactElement {
|
|||
<FlexRow spacing={1} align="left">
|
||||
<Checkbox
|
||||
checked={alerts?.raid?.enabled ?? false}
|
||||
onCheckedChange={(ev) =>
|
||||
dispatch(
|
||||
onCheckedChange={(ev) => {
|
||||
void dispatch(
|
||||
apiReducer.actions.twitchBotAlertsChanged({
|
||||
...alerts,
|
||||
raid: {
|
||||
|
@ -210,8 +210,8 @@ export default function ChatAlertsPage(): React.ReactElement {
|
|||
enabled: !!ev,
|
||||
},
|
||||
}),
|
||||
)
|
||||
}
|
||||
);
|
||||
}}
|
||||
id="raid-enabled"
|
||||
>
|
||||
<CheckboxIndicator>
|
||||
|
@ -232,7 +232,7 @@ export default function ChatAlertsPage(): React.ReactElement {
|
|||
disabled={!alerts?.raid?.enabled ?? true}
|
||||
required={alerts?.raid?.enabled ?? false}
|
||||
onChange={(messages) => {
|
||||
dispatch(
|
||||
void dispatch(
|
||||
apiReducer.actions.twitchBotAlertsChanged({
|
||||
...alerts,
|
||||
raid: { ...alerts.raid, messages },
|
||||
|
@ -248,8 +248,8 @@ export default function ChatAlertsPage(): React.ReactElement {
|
|||
<FlexRow spacing={1} align="left">
|
||||
<Checkbox
|
||||
checked={alerts?.cheer?.enabled ?? false}
|
||||
onCheckedChange={(ev) =>
|
||||
dispatch(
|
||||
onCheckedChange={(ev) => {
|
||||
void dispatch(
|
||||
apiReducer.actions.twitchBotAlertsChanged({
|
||||
...alerts,
|
||||
cheer: {
|
||||
|
@ -257,8 +257,8 @@ export default function ChatAlertsPage(): React.ReactElement {
|
|||
enabled: !!ev,
|
||||
},
|
||||
}),
|
||||
)
|
||||
}
|
||||
);
|
||||
}}
|
||||
id="raid-enabled"
|
||||
>
|
||||
<CheckboxIndicator>
|
||||
|
@ -279,7 +279,7 @@ export default function ChatAlertsPage(): React.ReactElement {
|
|||
disabled={!alerts?.cheer?.enabled ?? true}
|
||||
required={alerts?.cheer?.enabled ?? false}
|
||||
onChange={(messages) => {
|
||||
dispatch(
|
||||
void dispatch(
|
||||
apiReducer.actions.twitchBotAlertsChanged({
|
||||
...alerts,
|
||||
cheer: { ...alerts.cheer, messages },
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { CircleIcon, ExclamationTriangleIcon } from '@radix-ui/react-icons';
|
||||
import { CircleIcon } from '@radix-ui/react-icons';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PageContainer, SectionHeader, styled } from '../theme';
|
||||
|
@ -67,7 +67,7 @@ const Darken = styled('a', {
|
|||
function TwitchSection() {
|
||||
const { t } = useTranslation();
|
||||
const twitchInfo = useLiveKey<StreamInfo[]>('twitch/stream-info');
|
||||
//const twitchActivity = useLiveKey<StreamInfo[]>('twitch/chat-activity');
|
||||
// const twitchActivity = useLiveKey<StreamInfo[]>('twitch/chat-activity');
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -40,7 +40,7 @@ export default function DebugPage(): React.ReactElement {
|
|||
const [readValue, setReadValue] = useState('');
|
||||
const [writeKey, setWriteKey] = useState('');
|
||||
const [writeValue, setWriteValue] = useState('');
|
||||
const [writeErrorMsg, setWriteErrorMsg] = useState(null);
|
||||
const [writeErrorMsg, setWriteErrorMsg] = useState<string>(null);
|
||||
const api = useSelector((state: RootState) => state.api.client);
|
||||
|
||||
const performRead = async () => {
|
||||
|
@ -54,9 +54,11 @@ export default function DebugPage(): React.ReactElement {
|
|||
try {
|
||||
setWriteValue(JSON.stringify(JSON.parse(writeValue)));
|
||||
setWriteErrorMsg(null);
|
||||
} catch (e) {
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
setWriteErrorMsg(e.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
const dumpKeys = async () => {
|
||||
console.log(await api.keyList());
|
||||
|
@ -86,10 +88,20 @@ export default function DebugPage(): React.ReactElement {
|
|||
<Field size="fullWidth">
|
||||
<Label htmlFor="read-key">{t('pages.debug.console-ops')}</Label>
|
||||
<FlexRow align="left" spacing="1">
|
||||
<Button type="button" onClick={() => dumpKeys()}>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
void dumpKeys();
|
||||
}}
|
||||
>
|
||||
{t('pages.debug.dump-keys')}
|
||||
</Button>
|
||||
<Button type="button" onClick={() => dumpAll()}>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
void dumpAll();
|
||||
}}
|
||||
>
|
||||
{t('pages.debug.dump-all')}
|
||||
</Button>
|
||||
</FlexRow>
|
||||
|
@ -98,7 +110,7 @@ export default function DebugPage(): React.ReactElement {
|
|||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
if ((e.target as HTMLFormElement).checkValidity()) {
|
||||
performRead();
|
||||
void performRead();
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
@ -124,7 +136,7 @@ export default function DebugPage(): React.ReactElement {
|
|||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
if ((e.target as HTMLFormElement).checkValidity()) {
|
||||
performWrite();
|
||||
void performWrite();
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react';
|
||||
import { CheckIcon } from '@radix-ui/react-icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useModule, useStatus } from '../../lib/react-utils';
|
||||
import apiReducer, { modules } from '../../store/api/reducer';
|
||||
import {
|
||||
|
@ -19,11 +18,12 @@ import {
|
|||
} from '../theme';
|
||||
import SaveButton from '../components/utils/SaveButton';
|
||||
import Interval from '../components/Interval';
|
||||
import { useAppDispatch } from '../../store';
|
||||
|
||||
export default function LoyaltySettingsPage(): React.ReactElement {
|
||||
const { t } = useTranslation();
|
||||
const [config, setConfig, loadStatus] = useModule(modules.loyaltyConfig);
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
const status = useStatus(loadStatus.save);
|
||||
const busy =
|
||||
loadStatus.load?.type !== 'success' || loadStatus.save?.type === 'pending';
|
||||
|
@ -40,14 +40,14 @@ export default function LoyaltySettingsPage(): React.ReactElement {
|
|||
<FlexRow spacing={1}>
|
||||
<Checkbox
|
||||
checked={active}
|
||||
onCheckedChange={(ev) =>
|
||||
dispatch(
|
||||
onCheckedChange={(ev) => {
|
||||
void dispatch(
|
||||
setConfig({
|
||||
...config,
|
||||
enabled: !!ev,
|
||||
}),
|
||||
)
|
||||
}
|
||||
);
|
||||
}}
|
||||
id="enable"
|
||||
>
|
||||
<CheckboxIndicator>{active && <CheckIcon />}</CheckboxIndicator>
|
||||
|
@ -62,7 +62,7 @@ export default function LoyaltySettingsPage(): React.ReactElement {
|
|||
if (!(e.target as HTMLFormElement).checkValidity()) {
|
||||
return;
|
||||
}
|
||||
dispatch(setConfig(config));
|
||||
void dispatch(setConfig(config));
|
||||
}}
|
||||
>
|
||||
<Field size="fullWidth">
|
||||
|
@ -76,14 +76,14 @@ export default function LoyaltySettingsPage(): React.ReactElement {
|
|||
value={config?.currency ?? ''}
|
||||
disabled={!active || busy}
|
||||
required={true}
|
||||
onChange={(e) =>
|
||||
dispatch(
|
||||
onChange={(e) => {
|
||||
void dispatch(
|
||||
apiReducer.actions.loyaltyConfigChanged({
|
||||
...config,
|
||||
currency: e.target.value,
|
||||
}),
|
||||
)
|
||||
}
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<FieldNote>
|
||||
{t('pages.loyalty-settings.currency-name-hint')}
|
||||
|
@ -112,7 +112,7 @@ export default function LoyaltySettingsPage(): React.ReactElement {
|
|||
if (Number.isNaN(intNum)) {
|
||||
return;
|
||||
}
|
||||
dispatch(
|
||||
void dispatch(
|
||||
apiReducer.actions.loyaltyConfigChanged({
|
||||
...config,
|
||||
points: {
|
||||
|
@ -128,7 +128,7 @@ export default function LoyaltySettingsPage(): React.ReactElement {
|
|||
id="timer-interval"
|
||||
value={config?.points?.interval ?? 120}
|
||||
onChange={(interval) => {
|
||||
dispatch(
|
||||
void dispatch(
|
||||
apiReducer.actions.loyaltyConfigChanged({
|
||||
...(config ?? {}),
|
||||
points: {
|
||||
|
@ -161,7 +161,7 @@ export default function LoyaltySettingsPage(): React.ReactElement {
|
|||
if (Number.isNaN(intNum)) {
|
||||
return;
|
||||
}
|
||||
dispatch(
|
||||
void dispatch(
|
||||
apiReducer.actions.loyaltyConfigChanged({
|
||||
...config,
|
||||
points: {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useModule, useUserPoints } from '../../lib/react-utils';
|
||||
import { SortFunction } from '../../lib/type-utils';
|
||||
import { useAppDispatch } from '../../store';
|
||||
import { modules, removeRedeem, setUserPoints } from '../../store/api/reducer';
|
||||
import { DataTable } from '../components/DataTable';
|
||||
import DialogContent from '../components/DialogContent';
|
||||
|
@ -28,7 +28,7 @@ import { TableCell, TableRow } from '../theme/table';
|
|||
function RewardQueue() {
|
||||
const { t } = useTranslation();
|
||||
const [queue] = useModule(modules.loyaltyRedeemQueue);
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
// Big hack but this is required or refunds break
|
||||
useUserPoints();
|
||||
|
@ -42,11 +42,13 @@ function RewardQueue() {
|
|||
return a.display_name?.localeCompare(b.display_name);
|
||||
}
|
||||
case 'when': {
|
||||
return a.date?.getTime() - b.date?.getTime();
|
||||
return a.date && b.date ? a.date.getTime() - b.date.getTime() : 0;
|
||||
}
|
||||
case 'reward': {
|
||||
return a.reward?.name?.localeCompare(b.reward.name);
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -96,7 +98,7 @@ function RewardQueue() {
|
|||
]}
|
||||
defaultSort={{ key: 'when', order: 'desc' }}
|
||||
view={(entry) => (
|
||||
<TableRow key={entry.when + entry.username}>
|
||||
<TableRow key={`${entry.when.toString()}${entry.username}`}>
|
||||
<TableCell css={{ width: '22%', fontSize: '0.8rem' }}>
|
||||
{entry.date.toLocaleString()}
|
||||
</TableCell>
|
||||
|
@ -108,7 +110,7 @@ function RewardQueue() {
|
|||
<Button
|
||||
size="small"
|
||||
onClick={() => {
|
||||
dispatch(removeRedeem(entry));
|
||||
void dispatch(removeRedeem(entry));
|
||||
}}
|
||||
>
|
||||
{t('pages.loyalty-queue.accept')}
|
||||
|
@ -117,7 +119,7 @@ function RewardQueue() {
|
|||
size="small"
|
||||
onClick={() => {
|
||||
// Give points back to the viewer
|
||||
dispatch(
|
||||
void dispatch(
|
||||
setUserPoints({
|
||||
user: entry.username,
|
||||
points: entry.reward.price,
|
||||
|
@ -125,7 +127,7 @@ function RewardQueue() {
|
|||
}),
|
||||
);
|
||||
// Take the redeem off the list
|
||||
dispatch(removeRedeem(entry));
|
||||
void dispatch(removeRedeem(entry));
|
||||
}}
|
||||
>
|
||||
{t('pages.loyalty-queue.refund')}
|
||||
|
@ -143,7 +145,7 @@ function RewardQueue() {
|
|||
function UserList() {
|
||||
const { t } = useTranslation();
|
||||
const users = useUserPoints();
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [currentEntry, setCurrentEntry] = useState<UserEntry>(null);
|
||||
const [givePointDialog, setGivePointDialog] = useState({
|
||||
|
@ -188,7 +190,7 @@ function UserList() {
|
|||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
if ((e.target as HTMLFormElement).checkValidity()) {
|
||||
dispatch(
|
||||
void dispatch(
|
||||
setUserPoints({
|
||||
...givePointDialog,
|
||||
relative: true,
|
||||
|
@ -225,7 +227,7 @@ function UserList() {
|
|||
onChange={(e) =>
|
||||
setGivePointDialog({
|
||||
...givePointDialog,
|
||||
points: parseInt(e.target.value),
|
||||
points: parseInt(e.target.value, 10),
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
@ -258,7 +260,7 @@ function UserList() {
|
|||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
if ((e.target as HTMLFormElement).checkValidity()) {
|
||||
dispatch(
|
||||
void dispatch(
|
||||
setUserPoints({
|
||||
user: currentEntry.username,
|
||||
points: currentEntry.points,
|
||||
|
@ -290,7 +292,7 @@ function UserList() {
|
|||
onChange={(e) =>
|
||||
setCurrentEntry({
|
||||
...currentEntry,
|
||||
points: parseInt(e.target.value),
|
||||
points: parseInt(e.target.value, 10),
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { CheckIcon, PlusIcon } from '@radix-ui/react-icons';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useModule } from '../../lib/react-utils';
|
||||
import { useAppDispatch } from '../../store';
|
||||
import { modules } from '../../store/api/reducer';
|
||||
import { LoyaltyGoal, LoyaltyReward } from '../../store/api/types';
|
||||
import AlertContent from '../components/AlertContent';
|
||||
|
@ -267,7 +267,7 @@ function GoalItem({
|
|||
|
||||
function RewardsPage() {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
const [config] = useModule(modules.loyaltyConfig);
|
||||
const [rewards, setRewards] = useModule(modules.loyaltyRewards);
|
||||
const [filter, setFilter] = useState('');
|
||||
|
@ -282,12 +282,12 @@ function RewardsPage() {
|
|||
});
|
||||
const filterLC = filter.toLowerCase();
|
||||
|
||||
const deleteReward = (id: string): void => {
|
||||
dispatch(setRewards(rewards?.filter((r) => r.id !== id) ?? []));
|
||||
const deleteReward = (id: string) => {
|
||||
void dispatch(setRewards(rewards?.filter((r) => r.id !== id) ?? []));
|
||||
};
|
||||
|
||||
const toggleReward = (id: string): void => {
|
||||
dispatch(
|
||||
const toggleReward = (id: string) => {
|
||||
void dispatch(
|
||||
setRewards(
|
||||
rewards?.map((r) => {
|
||||
if (r.id === id) {
|
||||
|
@ -324,17 +324,17 @@ function RewardsPage() {
|
|||
if (!(e.target as HTMLFormElement).checkValidity()) {
|
||||
return;
|
||||
}
|
||||
const reward = dialogReward.reward;
|
||||
const { reward } = dialogReward;
|
||||
if (requiredInfo.enabled) {
|
||||
reward.required_info = requiredInfo.text;
|
||||
}
|
||||
const index = rewards?.findIndex((t) => t.id == reward.id);
|
||||
const index = rewards?.findIndex((r) => r.id === reward.id);
|
||||
if (index >= 0) {
|
||||
const newRewards = rewards.slice(0);
|
||||
newRewards[index] = reward;
|
||||
dispatch(setRewards(newRewards));
|
||||
void dispatch(setRewards(newRewards));
|
||||
} else {
|
||||
dispatch(setRewards([...(rewards ?? []), reward]));
|
||||
void dispatch(setRewards([...(rewards ?? []), reward]));
|
||||
}
|
||||
setDialogReward({ ...dialogReward, open: false });
|
||||
}}
|
||||
|
@ -450,7 +450,7 @@ function RewardsPage() {
|
|||
...dialogReward,
|
||||
reward: {
|
||||
...dialogReward?.reward,
|
||||
price: parseInt(e.target.value),
|
||||
price: parseInt(e.target.value, 10),
|
||||
},
|
||||
});
|
||||
}}
|
||||
|
@ -600,7 +600,7 @@ function RewardsPage() {
|
|||
|
||||
function GoalsPage() {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
const [config] = useModule(modules.loyaltyConfig);
|
||||
const [goals, setGoals] = useModule(modules.loyaltyGoals);
|
||||
const [filter, setFilter] = useState('');
|
||||
|
@ -609,18 +609,18 @@ function GoalsPage() {
|
|||
new: boolean;
|
||||
goal: LoyaltyGoal;
|
||||
}>({ open: false, new: false, goal: null });
|
||||
const [requiredInfo, setRequiredInfo] = useState({
|
||||
const [_requiredInfo, setRequiredInfo] = useState({
|
||||
enabled: false,
|
||||
text: '',
|
||||
});
|
||||
const filterLC = filter.toLowerCase();
|
||||
|
||||
const deleteGoal = (id: string): void => {
|
||||
dispatch(setGoals(goals?.filter((r) => r.id !== id) ?? []));
|
||||
void dispatch(setGoals(goals?.filter((r) => r.id !== id) ?? []));
|
||||
};
|
||||
|
||||
const toggleGoal = (id: string): void => {
|
||||
dispatch(
|
||||
void dispatch(
|
||||
setGoals(
|
||||
goals?.map((r) => {
|
||||
if (r.id === id) {
|
||||
|
@ -655,14 +655,14 @@ function GoalsPage() {
|
|||
if (!(e.target as HTMLFormElement).checkValidity()) {
|
||||
return;
|
||||
}
|
||||
const goal = dialogGoal.goal;
|
||||
const index = goals?.findIndex((t) => t.id == goal.id);
|
||||
const { goal } = dialogGoal;
|
||||
const index = goals?.findIndex((g) => g.id === goal.id);
|
||||
if (index >= 0) {
|
||||
const newGoals = goals.slice(0);
|
||||
newGoals[index] = goal;
|
||||
dispatch(setGoals(newGoals));
|
||||
void dispatch(setGoals(newGoals));
|
||||
} else {
|
||||
dispatch(setGoals([...(goals ?? []), goal]));
|
||||
void dispatch(setGoals([...(goals ?? []), goal]));
|
||||
}
|
||||
setDialogGoal({ ...dialogGoal, open: false });
|
||||
}}
|
||||
|
@ -776,7 +776,7 @@ function GoalsPage() {
|
|||
...dialogGoal,
|
||||
goal: {
|
||||
...dialogGoal?.goal,
|
||||
total: parseInt(e.target.value),
|
||||
total: parseInt(e.target.value, 10),
|
||||
},
|
||||
});
|
||||
}}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useModule, useStatus } from '../../lib/react-utils';
|
||||
import { useAppDispatch } from '../../store';
|
||||
import apiReducer, { modules } from '../../store/api/reducer';
|
||||
import SaveButton from '../components/utils/SaveButton';
|
||||
import {
|
||||
|
@ -19,7 +19,7 @@ export default function ServerSettingsPage(): React.ReactElement {
|
|||
modules.httpConfig,
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
const status = useStatus(loadStatus.save);
|
||||
const busy =
|
||||
loadStatus.load?.type !== 'success' || loadStatus.save?.type === 'pending';
|
||||
|
@ -31,7 +31,7 @@ export default function ServerSettingsPage(): React.ReactElement {
|
|||
</PageHeader>
|
||||
<form
|
||||
onSubmit={(ev) => {
|
||||
dispatch(setServerConfig(serverConfig));
|
||||
void dispatch(setServerConfig(serverConfig));
|
||||
ev.preventDefault();
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import React from 'react';
|
||||
import { keyframes } from '@stitches/react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { GitHubLogoIcon, TwitterLogoIcon } from '@radix-ui/react-icons';
|
||||
import { APPNAME, PageContainer, PageHeader, styled } from '../theme';
|
||||
|
||||
// @ts-expect-error Asset import
|
||||
import logo from '../../assets/icon-logo.svg';
|
||||
import { GitHubLogoIcon, TwitterLogoIcon } from '@radix-ui/react-icons';
|
||||
|
||||
const gradientAnimation = keyframes({
|
||||
'0%': {
|
||||
|
@ -22,10 +22,10 @@ const gradientAnimation = keyframes({
|
|||
const LogoPic = styled('div', {
|
||||
minHeight: '170px',
|
||||
width: '270px',
|
||||
maskImage: `url(${logo})`,
|
||||
maskImage: `url(${logo as string})`,
|
||||
maskRepeat: 'no-repeat',
|
||||
maskPosition: 'center',
|
||||
animation: `${gradientAnimation} 12s ease infinite`,
|
||||
animation: `${gradientAnimation()} 12s ease infinite`,
|
||||
backgroundSize: '400% 400%',
|
||||
backgroundImage: `linear-gradient(
|
||||
45deg,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { CheckIcon } from '@radix-ui/react-icons';
|
||||
import React from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useModule, useStatus } from '../../lib/react-utils';
|
||||
import { useAppDispatch } from '../../store';
|
||||
import apiReducer, { modules } from '../../store/api/reducer';
|
||||
import DefinitionTable from '../components/DefinitionTable';
|
||||
import SaveButton from '../components/utils/SaveButton';
|
||||
|
@ -48,15 +48,15 @@ function TwitchBotSettings() {
|
|||
);
|
||||
const [twitchConfig, setTwitchConfig] = useModule(modules.twitchConfig);
|
||||
const status = useStatus(loadStatus.save);
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const active = twitchConfig?.enable_bot ?? false;
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={(ev) => {
|
||||
dispatch(setTwitchConfig(twitchConfig));
|
||||
dispatch(setBotConfig(botConfig));
|
||||
void dispatch(setTwitchConfig(twitchConfig));
|
||||
void dispatch(setBotConfig(botConfig));
|
||||
ev.preventDefault();
|
||||
}}
|
||||
>
|
||||
|
@ -189,12 +189,12 @@ function TwitchAPISettings() {
|
|||
modules.twitchConfig,
|
||||
);
|
||||
const status = useStatus(loadStatus.save);
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={(ev) => {
|
||||
dispatch(setTwitchConfig(twitchConfig));
|
||||
void dispatch(setTwitchConfig(twitchConfig));
|
||||
ev.preventDefault();
|
||||
}}
|
||||
>
|
||||
|
@ -281,7 +281,7 @@ function TwitchAPISettings() {
|
|||
export default function TwitchSettingsPage(): React.ReactElement {
|
||||
const { t } = useTranslation();
|
||||
const [twitchConfig, setTwitchConfig] = useModule(modules.twitchConfig);
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const active = twitchConfig?.enabled ?? false;
|
||||
|
||||
|
@ -294,14 +294,14 @@ export default function TwitchSettingsPage(): React.ReactElement {
|
|||
<FlexRow spacing={1}>
|
||||
<Checkbox
|
||||
checked={active}
|
||||
onCheckedChange={(ev) =>
|
||||
dispatch(
|
||||
onCheckedChange={(ev) => {
|
||||
void dispatch(
|
||||
setTwitchConfig({
|
||||
...twitchConfig,
|
||||
enabled: !!ev,
|
||||
}),
|
||||
)
|
||||
}
|
||||
);
|
||||
}}
|
||||
id="enable"
|
||||
>
|
||||
<CheckboxIndicator>{active && <CheckIcon />}</CheckboxIndicator>
|
||||
|
|
|
@ -22,7 +22,7 @@ export const AlertOverlay = styled(AlertDialogPrimitive.Overlay, {
|
|||
position: 'fixed',
|
||||
inset: 0,
|
||||
'@media (prefers-reduced-motion: no-preference)': {
|
||||
animation: `${overlayShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
|
||||
animation: `${overlayShow()} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -40,7 +40,7 @@ export const AlertContainer = styled(AlertDialogPrimitive.Content, {
|
|||
maxHeight: '85vh',
|
||||
padding: '1rem',
|
||||
'@media (prefers-reduced-motion: no-preference)': {
|
||||
animation: `${contentShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
|
||||
animation: `${contentShow()} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
|
||||
},
|
||||
border: '2px solid $teal8',
|
||||
'&:focus': { outline: 'none' },
|
||||
|
|
|
@ -21,7 +21,7 @@ export const DialogOverlay = styled(DialogPrimitive.Overlay, {
|
|||
position: 'fixed',
|
||||
inset: 0,
|
||||
'@media (prefers-reduced-motion: no-preference)': {
|
||||
animation: `${overlayShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
|
||||
animation: `${overlayShow()} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -40,7 +40,7 @@ export const DialogContainer = styled(DialogPrimitive.Content, {
|
|||
padding: '1rem',
|
||||
overflow: 'auto',
|
||||
'@media (prefers-reduced-motion: no-preference)': {
|
||||
animation: `${contentShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
|
||||
animation: `${contentShow()} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
|
||||
},
|
||||
'&:focus': { outline: 'none' },
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { theme, styled } from './theme';
|
||||
import { styled } from './theme';
|
||||
|
||||
export const Table = styled('table', {
|
||||
borderCollapse: 'collapse',
|
||||
|
|
4
frontend/wailsjs/go/main/App.d.ts
vendored
Normal file
4
frontend/wailsjs/go/main/App.d.ts
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
export function GetHubPassword(arg1:number):Promise<void>;
|
7
frontend/wailsjs/go/main/App.js
Normal file
7
frontend/wailsjs/go/main/App.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
// @ts-check
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
export function GetHubPassword(arg1) {
|
||||
return window['go']['main']['App']['GetHubPassword'](arg1);
|
||||
}
|
Loading…
Reference in a new issue