1
0
Fork 0
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:
Ash Keel 2022-11-18 15:46:50 +01:00
parent 05cda342fd
commit 94d57b9a70
No known key found for this signature in database
GPG key ID: BAD8D93E7314ED3E
32 changed files with 6944 additions and 2034 deletions

1
driver.interface.go Normal file
View file

@ -0,0 +1 @@
package main

View file

@ -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/*'],
};

File diff suppressed because it is too large Load diff

View file

@ -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"
}
}

View file

@ -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'),
);

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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",

View file

@ -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,

View file

@ -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':

View file

@ -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;

View file

@ -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;

View file

@ -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,
}),
);
}

View file

@ -75,6 +75,7 @@ export function DataTable<T>({
case 'desc':
return -result;
}
return 0;
});
}

View file

@ -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}

View file

@ -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,20 +93,19 @@ 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', {
...data,
subscription: {
...data.subscription,
created_at: new Date().toISOString(),
},
});
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(),
},
});
};
@ -139,7 +113,7 @@ function WebhookIntegration() {
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>

View file

@ -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]: {

View file

@ -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()

View file

@ -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 },

View file

@ -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 (
<>

View file

@ -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,8 +54,10 @@ export default function DebugPage(): React.ReactElement {
try {
setWriteValue(JSON.stringify(JSON.parse(writeValue)));
setWriteErrorMsg(null);
} catch (e) {
setWriteErrorMsg(e.message);
} catch (e: unknown) {
if (e instanceof Error) {
setWriteErrorMsg(e.message);
}
}
};
const dumpKeys = async () => {
@ -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();
}
}}
>

View file

@ -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: {

View file

@ -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),
})
}
/>

View file

@ -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),
},
});
}}

View file

@ -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();
}}
>

View file

@ -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,

View file

@ -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>

View file

@ -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' },

View file

@ -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' },
});

View file

@ -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
View 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>;

View 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);
}