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