diff --git a/.eslintrc.json b/.eslintrc.json
index 697a1e8..32c6a62 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -13,6 +13,7 @@
},
"plugins": [
"react",
+ "react-hooks",
"jsx-a11y",
"import"
],
@@ -32,7 +33,9 @@
"no-mixed-operators":"off",
"react/prop-types": "off",
"react/jsx-one-expression-per-line": "off",
- "react/jsx-closing-tag-location":"off",
+ "react/jsx-closing-tag-location": "off",
+ "react/jsx-key": "warn",
+ "react-hooks/rules-of-hooks": "error",
"jsx-a11y/click-events-have-key-events":"off",
"jsx-a11y/no-static-element-interactions":"off",
"no-continue": "off",
diff --git a/package-lock.json b/package-lock.json
index 5bdb990..c0cf782 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -80,6 +80,7 @@
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-jsx-a11y": "^6.6.0",
"eslint-plugin-react": "^7.30.1",
+ "eslint-plugin-react-hooks": "^4.6.0",
"generate-package-json-webpack-plugin": "^2.6.0",
"mkdirp": "^1.0.4",
"ttag-cli": "^1.9.3",
@@ -5064,7 +5065,6 @@
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz",
"integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==",
"dev": true,
- "peer": true,
"engines": {
"node": ">=10"
},
@@ -15151,7 +15151,6 @@
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz",
"integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==",
"dev": true,
- "peer": true,
"requires": {}
},
"eslint-scope": {
diff --git a/package.json b/package.json
index c611b89..ae93d87 100644
--- a/package.json
+++ b/package.json
@@ -94,6 +94,7 @@
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-jsx-a11y": "^6.6.0",
"eslint-plugin-react": "^7.30.1",
+ "eslint-plugin-react-hooks": "^4.6.0",
"generate-package-json-webpack-plugin": "^2.6.0",
"mkdirp": "^1.0.4",
"ttag-cli": "^1.9.3",
diff --git a/src/components/Alert.jsx b/src/components/Alert.jsx
index d21a255..d486a12 100644
--- a/src/components/Alert.jsx
+++ b/src/components/Alert.jsx
@@ -6,17 +6,18 @@ import React, { useState, useEffect, useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import GlobalCaptcha from './GlobalCaptcha';
+import BanInfo from './BanInfo';
import { closeAlert } from '../store/actions';
const Alert = () => {
const [render, setRender] = useState(false);
const {
- alertOpen,
+ open,
alertType,
- alertTitle,
- alertMessage,
- alertBtn,
+ title,
+ message,
+ btn,
} = useSelector((state) => state.alert);
const dispatch = useDispatch();
@@ -25,20 +26,32 @@ const Alert = () => {
}, [dispatch]);
const onTransitionEnd = () => {
- if (!alertOpen) setRender(false);
+ if (!open) setRender(false);
};
useEffect(() => {
window.setTimeout(() => {
- if (alertOpen) setRender(true);
+ if (open) setRender(true);
}, 10);
- }, [alertOpen]);
+ }, [open]);
+
+ let Content = null;
+ switch (alertType) {
+ case 'captcha':
+ Content = GlobalCaptcha;
+ break;
+ case 'ban':
+ Content = BanInfo;
+ break;
+ default:
+ // nothing
+ }
return (
- (render || alertOpen) && (
+ (render || open) && (
{
onClick={close}
/>
-
{alertTitle}
+
{title}
- {alertMessage}
+ {message}
- {(alertType === 'captcha')
- ?
- : (
-
- )}
+ {(Content) ? (
+
+ ) : (
+
+ )}
diff --git a/src/components/BanInfo.jsx b/src/components/BanInfo.jsx
new file mode 100644
index 0000000..6281581
--- /dev/null
+++ b/src/components/BanInfo.jsx
@@ -0,0 +1,100 @@
+/*
+ * get information about ban
+ */
+
+import React, { useState } from 'react';
+import { t } from 'ttag';
+
+import useInterval from './hooks/interval';
+import {
+ largeDurationToString,
+} from '../core/utils';
+import { requestBanInfo } from '../store/actions/fetch';
+
+
+const BanInfo = ({ close }) => {
+ const [errors, setErrors] = useState([]);
+ const [reason, setReason] = useState('');
+ const [expireTs, setExpireTs] = useState(0);
+ const [expire, setExpire] = useState(null);
+ const [submitting, setSubmitting] = useState(false);
+
+ const handleSubmit = async (evt) => {
+ evt.preventDefault();
+ if (submitting) {
+ return;
+ }
+ setSubmitting(true);
+ const info = await requestBanInfo();
+ setSubmitting(false);
+ if (info.errors) {
+ setErrors(info.errors);
+ return;
+ }
+ const {
+ ts,
+ reason: newReason,
+ } = info;
+ setExpireTs(ts);
+ const tsDate = new Date(ts);
+ setExpire(tsDate.toLocaleString);
+ setReason(newReason);
+ };
+
+ useInterval(() => {
+ console.log('do');
+ if (expireTs) {
+ setExpireTs(expireTs - 1);
+ }
+ }, 1000);
+
+ return (
+
+ {errors.map((error) => (
+
+ {t`Error`}: {error}
+
+ ))}
+ {(reason) && (
+ <>
+
{t`Reason`}:
+
{reason}
+ >
+ )}
+ {(expireTs) && (
+ <>
+
{t`Duration`}:
+
+ {t`Your ban expires at `}
+ {expire}
+ {t` which is in `}
+
+ {largeDurationToString(expireTs)}
+
+
+ >
+ )}
+
+
+
+
+
+
+ );
+};
+
+export default React.memo(BanInfo);
diff --git a/src/components/ChatMessage.jsx b/src/components/ChatMessage.jsx
index 58c77d9..b36ab07 100644
--- a/src/components/ChatMessage.jsx
+++ b/src/components/ChatMessage.jsx
@@ -19,10 +19,6 @@ function ChatMessage({
msg,
ts,
}) {
- if (!name) {
- return null;
- }
-
const dispatch = useDispatch();
const isDarkMode = useSelector(
(state) => state.gui.style.indexOf('dark') !== -1,
diff --git a/src/components/LanguageSelect.jsx b/src/components/LanguageSelect.jsx
index 101357e..8164ded 100644
--- a/src/components/LanguageSelect.jsx
+++ b/src/components/LanguageSelect.jsx
@@ -11,10 +11,6 @@ import { t } from 'ttag';
import { MONTH } from '../core/constants';
function LanguageSelect() {
- if (!navigator.cookieEnabled) {
- return null;
- }
-
const { lang, langs } = window.ssv;
const [langSel, setLangSel] = useState(lang);
@@ -30,6 +26,10 @@ function LanguageSelect() {
}
}, [langSel]);
+ if (!navigator.cookieEnabled) {
+ return null;
+ }
+
return (
diff --git a/src/components/ModCanvastools.jsx b/src/components/ModCanvastools.jsx
index 65e7905..b5394a2 100644
--- a/src/components/ModCanvastools.jsx
+++ b/src/components/ModCanvastools.jsx
@@ -240,7 +240,7 @@ function ModCanvastools() {
)}
-
Choose Canvas:
+
{t`Choose Canvas`}:
diff --git a/src/components/ModIIDtools.jsx b/src/components/ModIIDtools.jsx
index 54285c5..44cd314 100644
--- a/src/components/ModIIDtools.jsx
+++ b/src/components/ModIIDtools.jsx
@@ -5,13 +5,20 @@
import React, { useState } from 'react';
import { t } from 'ttag';
+import { parseInterval } from '../core/utils';
+
async function submitIIDAction(
action,
iid,
+ reason,
+ duration,
callback,
) {
+ const time = Date.now() + parseInterval(duration);
const data = new FormData();
data.append('iidaction', action);
+ data.append('reason', reason);
+ data.append('time', time);
data.append('iid', iid);
const resp = await fetch('./api/modtools', {
credentials: 'include',
@@ -24,6 +31,8 @@ async function submitIIDAction(
function ModIIDtools() {
const [iIDAction, selectIIDAction] = useState('givecaptcha');
const [iid, selectIid] = useState('');
+ const [reason, setReason] = useState('');
+ const [duration, setDuration] = useState('1d');
const [resp, setResp] = useState('');
const [submitting, setSubmitting] = useState(false);
@@ -37,15 +46,47 @@ function ModIIDtools() {
selectIIDAction(sel.options[sel.selectedIndex].value);
}}
>
- {['givecaptcha']
+ {['givecaptcha', 'ban', 'unban', 'whitelist', 'unwhitelist']
.map((opt) => (
))}
+ {(iIDAction === 'ban') && (
+ <>
+
{t`Reason`}
+
{
+ setReason(evt.target.value.trim());
+ }}
+ />
+
+ {`${t`Duration`}: `}
+ {
+ setDuration(evt.target.value.trim());
+ }}
+ />
+ {t`(0 = infinite)`}
+
+ >
+ )}
{' IID: '}
{
- const newIid = evt.target.value.trim();
- selectIid(newIid);
+ selectIid(evt.target.value.trim());
}}
/>