mirror of
https://github.com/orangecoding/fredy.git
synced 2026-06-16 12:31:07 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d87aeb5f9 | ||
|
|
9774989eeb | ||
|
|
9db1ffd8eb | ||
|
|
1cb79d1287 | ||
|
|
212d6e0367 | ||
|
|
97cb6fa5eb | ||
|
|
8d2cc7f3e0 | ||
|
|
3de81903a1 | ||
|
|
1ad79230c2 | ||
|
|
fb19c52b0f | ||
|
|
db12d33910 |
@@ -1,3 +0,0 @@
|
|||||||
/ui/public
|
|
||||||
/db/
|
|
||||||
/conf/
|
|
||||||
281
.eslintrc.cjs
281
.eslintrc.cjs
@@ -1,281 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
env: {
|
|
||||||
es2021: true,
|
|
||||||
node: true,
|
|
||||||
browser: true,
|
|
||||||
mocha: true,
|
|
||||||
},
|
|
||||||
parser: '@babel/eslint-parser',
|
|
||||||
extends: ['eslint:recommended', 'prettier'],
|
|
||||||
plugins: ['react'],
|
|
||||||
globals: {
|
|
||||||
Promise: false,
|
|
||||||
describe: true,
|
|
||||||
after: true,
|
|
||||||
it: true,
|
|
||||||
fetch: true,
|
|
||||||
},
|
|
||||||
parserOptions: {
|
|
||||||
sourceType: 'module',
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
eqeqeq: [2, 'allow-null'],
|
|
||||||
|
|
||||||
// ###########################################################
|
|
||||||
// ### Semantics / Performance impacting
|
|
||||||
// ###########################################################
|
|
||||||
// babel inserts `'use strict';` for us
|
|
||||||
strict: 0,
|
|
||||||
|
|
||||||
'no-redeclare': [2, { builtinGlobals: false }],
|
|
||||||
|
|
||||||
// If a class method does not use this, it can safely be made a static function.
|
|
||||||
// http://eslint.org/docs/rules/class-methods-use-this
|
|
||||||
'class-methods-use-this': ['off'],
|
|
||||||
|
|
||||||
// ###########################################################
|
|
||||||
// ### Style
|
|
||||||
// ###########################################################
|
|
||||||
indent: ['off', 2],
|
|
||||||
'linebreak-style': ['error', 'unix'],
|
|
||||||
quotes: ['error', 'single', { avoidEscape: true, allowTemplateLiterals: true }],
|
|
||||||
semi: ['error', 'always'],
|
|
||||||
'no-console': ['error', { allow: ['warn', 'error'] }],
|
|
||||||
|
|
||||||
// ###########################################################
|
|
||||||
// ### React
|
|
||||||
// ###########################################################
|
|
||||||
// Specify whether double or single quotes should be used in JSX attributes
|
|
||||||
// http://eslint.org/docs/rules/jsx-quotes
|
|
||||||
'jsx-quotes': ['error', 'prefer-double'],
|
|
||||||
|
|
||||||
// Prevent missing displayName in a React component definition
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/display-name.md
|
|
||||||
'react/display-name': ['off'],
|
|
||||||
|
|
||||||
// Forbid certain propTypes (any, array, object)
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/forbid-prop-types.md
|
|
||||||
'react/forbid-prop-types': 'off',
|
|
||||||
|
|
||||||
// Validate closing bracket location in JSX
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-closing-bracket-location.md
|
|
||||||
'react/jsx-closing-bracket-location': ['off'],
|
|
||||||
|
|
||||||
// Enforce or disallow spaces inside of curly braces in JSX attributes
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-curly-spacing.md
|
|
||||||
'react/jsx-curly-spacing': ['off'],
|
|
||||||
|
|
||||||
// Enforce event handler naming conventions in JSX
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-handler-names.md
|
|
||||||
'react/jsx-handler-names': [
|
|
||||||
'off',
|
|
||||||
{
|
|
||||||
eventHandlerPrefix: 'handle',
|
|
||||||
eventHandlerPropPrefix: 'on',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
// Validate props indentation in JSX
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-indent-props.md
|
|
||||||
'react/jsx-indent-props': 'off',
|
|
||||||
|
|
||||||
// Validate JSX has key prop when in array or iterator
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-key.md
|
|
||||||
'react/jsx-key': 'off',
|
|
||||||
|
|
||||||
// Limit maximum of props on a single line in JSX
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-max-props-per-line.md
|
|
||||||
'react/jsx-max-props-per-line': ['off'],
|
|
||||||
|
|
||||||
// Prevent usage of .bind() in JSX props
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md
|
|
||||||
'react/jsx-no-bind': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
ignoreRefs: true,
|
|
||||||
allowArrowFunctions: true,
|
|
||||||
allowBind: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
// Prevent duplicate props in JSX
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-duplicate-props.md
|
|
||||||
'react/jsx-no-duplicate-props': ['error', { ignoreCase: true }],
|
|
||||||
|
|
||||||
// Prevent usage of unwrapped JSX strings
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-literals.md
|
|
||||||
'react/jsx-no-literals': 'off',
|
|
||||||
|
|
||||||
// Disallow undeclared variables in JSX
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-undef.md
|
|
||||||
'react/jsx-no-undef': 'error',
|
|
||||||
|
|
||||||
// Enforce PascalCase for user-defined JSX components
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-pascal-case.md
|
|
||||||
'react/jsx-pascal-case': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
allowAllCaps: true,
|
|
||||||
ignore: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
// Enforce propTypes declarations alphabetical sorting
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/sort-prop-types.md
|
|
||||||
'react/sort-prop-types': [
|
|
||||||
'off',
|
|
||||||
{
|
|
||||||
ignoreCase: true,
|
|
||||||
callbacksLast: false,
|
|
||||||
requiredFirst: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
// Deprecated in favor of react/jsx-sort-props
|
|
||||||
'react/jsx-sort-prop-types': 'off',
|
|
||||||
|
|
||||||
// Enforce props alphabetical sorting
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-sort-props.md
|
|
||||||
'react/jsx-sort-props': 'off',
|
|
||||||
|
|
||||||
// Prevent React to be incorrectly marked as unused
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-react.md
|
|
||||||
'react/jsx-uses-react': 'error',
|
|
||||||
|
|
||||||
// Prevent variables used in JSX to be incorrectly marked as unused
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-vars.md
|
|
||||||
'react/jsx-uses-vars': 'error',
|
|
||||||
|
|
||||||
// Prevent usage of dangerous JSX properties
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-danger.md
|
|
||||||
'react/no-danger': 'warn',
|
|
||||||
|
|
||||||
// Prevent usage of deprecated methods
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-deprecated.md
|
|
||||||
'react/no-deprecated': ['error'],
|
|
||||||
|
|
||||||
// Prevent usage of setState in componentDidMount
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-did-mount-set-state.md
|
|
||||||
'react/no-did-mount-set-state': ['error'],
|
|
||||||
|
|
||||||
// Prevent usage of setState in componentDidUpdate
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-did-update-set-state.md
|
|
||||||
'react/no-did-update-set-state': ['warn'],
|
|
||||||
|
|
||||||
// Prevent direct mutation of this.state
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-direct-mutation-state.md
|
|
||||||
'react/no-direct-mutation-state': 'off',
|
|
||||||
|
|
||||||
// Prevent usage of isMounted
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-is-mounted.md
|
|
||||||
'react/no-is-mounted': 'error',
|
|
||||||
|
|
||||||
// Prevent usage of setState
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-set-state.md
|
|
||||||
'react/no-set-state': 'off',
|
|
||||||
|
|
||||||
// Prevent using string references
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-string-refs.md
|
|
||||||
'react/no-string-refs': 'warn',
|
|
||||||
|
|
||||||
// Prevent usage of unknown DOM property
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unknown-property.md
|
|
||||||
'react/no-unknown-property': 'error',
|
|
||||||
|
|
||||||
// Prevent missing props validation in a React component definition
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prop-types.md
|
|
||||||
'react/prop-types': ['error', { ignore: [], customValidators: [], skipUndeclared: true }],
|
|
||||||
|
|
||||||
// Prevent missing React when using JSX
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/react-in-jsx-scope.md
|
|
||||||
'react/react-in-jsx-scope': 'error',
|
|
||||||
|
|
||||||
// Restrict file extensions that may be required
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/require-extension.md
|
|
||||||
// deprecated in favor of import/extensions
|
|
||||||
'react/require-extension': ['off', { extensions: ['.jsx', '.js'] }],
|
|
||||||
|
|
||||||
// Require render() methods to return something
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/require-render-return.md
|
|
||||||
'react/require-render-return': 'error',
|
|
||||||
|
|
||||||
// Prevent extra closing tags for components without children
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/self-closing-comp.md
|
|
||||||
'react/self-closing-comp': 'warn',
|
|
||||||
|
|
||||||
// Enforce component methods order
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/sort-comp.md
|
|
||||||
'react/sort-comp': 'off',
|
|
||||||
|
|
||||||
// Prevent missing parentheses around multilines JSX
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-wrap-multilines.md
|
|
||||||
'react/jsx-wrap-multilines': [
|
|
||||||
'warn',
|
|
||||||
{
|
|
||||||
declaration: true,
|
|
||||||
assignment: true,
|
|
||||||
return: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'react/wrap-multilines': 'off', // deprecated version
|
|
||||||
|
|
||||||
// Require that the first prop in a JSX element be on a new line when the element is multiline
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-first-prop-new-line.md
|
|
||||||
'react/jsx-first-prop-new-line': ['off'],
|
|
||||||
|
|
||||||
// Enforce spacing around jsx equals signs
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-equals-spacing.md
|
|
||||||
'react/jsx-equals-spacing': ['warn', 'never'],
|
|
||||||
|
|
||||||
// Disallow target="_blank" on links
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-target-blank.md
|
|
||||||
'react/jsx-no-target-blank': 'error',
|
|
||||||
|
|
||||||
// only .jsx files may have JSX
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-filename-extension.md
|
|
||||||
'react/jsx-filename-extension': ['error', { extensions: ['.jsx'] }],
|
|
||||||
|
|
||||||
// prevent accidental JS comments from being injected into JSX as text
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-comment-textnodes.md
|
|
||||||
'react/jsx-no-comment-textnodes': 'error',
|
|
||||||
'react/no-comment-textnodes': 'off', // deprecated version
|
|
||||||
|
|
||||||
// disallow using React.render/ReactDOM.render's return value
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-render-return-value.md
|
|
||||||
'react/no-render-return-value': 'error',
|
|
||||||
|
|
||||||
// require a shouldComponentUpdate method, or PureRenderMixin
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/require-optimization.md
|
|
||||||
'react/require-optimization': ['off', { allowDecorators: [] }],
|
|
||||||
|
|
||||||
// warn against using findDOMNode()
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-find-dom-node.md
|
|
||||||
'react/no-find-dom-node': 'warn',
|
|
||||||
|
|
||||||
// Forbid certain props on Components
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/forbid-component-props.md
|
|
||||||
'react/forbid-component-props': ['off', { forbid: [] }],
|
|
||||||
|
|
||||||
// Prevent problem with children and props.dangerouslySetInnerHTML
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-danger-with-children.md
|
|
||||||
'react/no-danger-with-children': 'error',
|
|
||||||
|
|
||||||
// Prevent unused propType definitions
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unused-prop-types.md
|
|
||||||
'react/no-unused-prop-types': [
|
|
||||||
'warn',
|
|
||||||
{
|
|
||||||
customValidators: [],
|
|
||||||
skipShapeProps: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
// Require style prop value be an object or var
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/style-prop-object.md
|
|
||||||
'react/style-prop-object': 'error',
|
|
||||||
|
|
||||||
// Prevent passing of children as props
|
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-children-prop.md
|
|
||||||
'react/no-children-prop': 'warn',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,7 +1 @@
|
|||||||
{
|
{"interval":"60","port":9998,"workingHours":{"from":"","to":""},"demoMode":false,"analyticsEnabled":false}
|
||||||
"interval": "60",
|
|
||||||
"port": 9998,
|
|
||||||
"workingHours": { "from": "", "to": "" },
|
|
||||||
"demoMode": false,
|
|
||||||
"analyticsEnabled": null
|
|
||||||
}
|
|
||||||
96
eslint.config.js
Normal file
96
eslint.config.js
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
// eslint.config.js
|
||||||
|
import js from '@eslint/js';
|
||||||
|
import prettier from 'eslint-config-prettier';
|
||||||
|
import globals from 'globals';
|
||||||
|
import react from 'eslint-plugin-react';
|
||||||
|
import babelParser from '@babel/eslint-parser';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
files: ['**/*.{js,jsx,ts,tsx}'],
|
||||||
|
ignores: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/public/**', 'db/**', 'conf/**'],
|
||||||
|
},
|
||||||
|
js.configs.recommended,
|
||||||
|
prettier,
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
parser: babelParser,
|
||||||
|
sourceType: 'module',
|
||||||
|
ecmaVersion: 2021,
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.node,
|
||||||
|
...globals.mocha,
|
||||||
|
Promise: 'readonly',
|
||||||
|
fetch: 'readonly',
|
||||||
|
describe: 'readonly',
|
||||||
|
after: 'readonly',
|
||||||
|
it: 'readonly',
|
||||||
|
},
|
||||||
|
parserOptions: { requireConfigFile: false },
|
||||||
|
},
|
||||||
|
plugins: { react },
|
||||||
|
rules: {
|
||||||
|
eqeqeq: [2, 'allow-null'],
|
||||||
|
strict: 0,
|
||||||
|
'no-redeclare': [2, { builtinGlobals: false }],
|
||||||
|
'class-methods-use-this': 'off',
|
||||||
|
indent: ['off', 2],
|
||||||
|
'linebreak-style': ['error', 'unix'],
|
||||||
|
quotes: ['error', 'single', { avoidEscape: true, allowTemplateLiterals: true }],
|
||||||
|
semi: ['error', 'always'],
|
||||||
|
'no-console': ['error', { allow: ['warn', 'error'] }],
|
||||||
|
'jsx-quotes': ['error', 'prefer-double'],
|
||||||
|
'react/display-name': 'off',
|
||||||
|
'react/forbid-prop-types': 'off',
|
||||||
|
'react/jsx-closing-bracket-location': 'off',
|
||||||
|
'react/jsx-curly-spacing': 'off',
|
||||||
|
'react/jsx-handler-names': ['off', { eventHandlerPrefix: 'handle', eventHandlerPropPrefix: 'on' }],
|
||||||
|
'react/jsx-indent-props': 'off',
|
||||||
|
'react/jsx-key': 'off',
|
||||||
|
'react/jsx-max-props-per-line': 'off',
|
||||||
|
'react/jsx-no-bind': ['error', { ignoreRefs: true, allowArrowFunctions: true, allowBind: false }],
|
||||||
|
'react/jsx-no-duplicate-props': ['error', { ignoreCase: true }],
|
||||||
|
'react/jsx-no-literals': 'off',
|
||||||
|
'react/jsx-no-undef': 'error',
|
||||||
|
'react/jsx-pascal-case': ['error', { allowAllCaps: true, ignore: [] }],
|
||||||
|
'react/sort-prop-types': ['off', { ignoreCase: true, callbacksLast: false, requiredFirst: false }],
|
||||||
|
'react/jsx-sort-prop-types': 'off',
|
||||||
|
'react/jsx-sort-props': 'off',
|
||||||
|
'react/jsx-uses-react': 'error',
|
||||||
|
'react/jsx-uses-vars': 'error',
|
||||||
|
'react/no-danger': 'warn',
|
||||||
|
'react/no-deprecated': 'error',
|
||||||
|
'react/no-did-mount-set-state': 'error',
|
||||||
|
'react/no-did-update-set-state': 'warn',
|
||||||
|
'react/no-direct-mutation-state': 'off',
|
||||||
|
'react/no-is-mounted': 'error',
|
||||||
|
'react/no-set-state': 'off',
|
||||||
|
'react/no-string-refs': 'warn',
|
||||||
|
'react/no-unknown-property': 'error',
|
||||||
|
'react/prop-types': ['error', { ignore: [], customValidators: [], skipUndeclared: true }],
|
||||||
|
'react/react-in-jsx-scope': 'error',
|
||||||
|
'react/require-extension': 'off',
|
||||||
|
'react/require-render-return': 'error',
|
||||||
|
'react/self-closing-comp': 'warn',
|
||||||
|
'react/sort-comp': 'off',
|
||||||
|
'react/jsx-wrap-multilines': ['warn', { declaration: true, assignment: true, return: true }],
|
||||||
|
'react/wrap-multilines': 'off',
|
||||||
|
'react/jsx-first-prop-new-line': 'off',
|
||||||
|
'react/jsx-equals-spacing': ['warn', 'never'],
|
||||||
|
'react/jsx-no-target-blank': 'error',
|
||||||
|
'react/jsx-filename-extension': ['error', { extensions: ['.jsx'] }],
|
||||||
|
'react/jsx-no-comment-textnodes': 'error',
|
||||||
|
'react/no-comment-textnodes': 'off',
|
||||||
|
'react/no-render-return-value': 'error',
|
||||||
|
'react/require-optimization': ['off', { allowDecorators: [] }],
|
||||||
|
'react/no-find-dom-node': 'warn',
|
||||||
|
'react/forbid-component-props': ['off', { forbid: [] }],
|
||||||
|
'react/no-danger-with-children': 'error',
|
||||||
|
'react/no-unused-prop-types': ['warn', { customValidators: [], skipShapeProps: true }],
|
||||||
|
'react/style-prop-object': 'error',
|
||||||
|
'react/no-children-prop': 'warn',
|
||||||
|
},
|
||||||
|
settings: { react: { version: 'detect' } },
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -59,9 +59,7 @@ class FredyRuntime {
|
|||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
reject(err);
|
reject(err);
|
||||||
/* eslint-disable no-console */
|
|
||||||
console.error(err);
|
console.error(err);
|
||||||
/* eslint-enable no-console */
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ export const send = ({ serviceName, newListings, notificationConfig, jobKey }) =
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
};
|
};
|
||||||
export const config = {
|
export const config = {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ let appliedBlackList = [];
|
|||||||
function normalize(o) {
|
function normalize(o) {
|
||||||
const size = o.size != null ? o.size.replace('Wohnfläche ', '') : 'N/A m²';
|
const size = o.size != null ? o.size.replace('Wohnfläche ', '') : 'N/A m²';
|
||||||
const price = o.price.replace('Kaufpreis ', '');
|
const price = o.price.replace('Kaufpreis ', '');
|
||||||
const address = o.address.split(' • ')[o.address.split(' • ').length - 1];
|
const address = o.address?.split(' • ')?.pop() ?? null;
|
||||||
const title = o.title || 'No title available';
|
const title = o.title || 'No title available';
|
||||||
const link = config.url;
|
const link = config.url;
|
||||||
const id = buildHash(title, price);
|
const id = buildHash(title, price);
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ async function getListings(url) {
|
|||||||
.map((expose) => {
|
.map((expose) => {
|
||||||
const item = expose.item;
|
const item = expose.item;
|
||||||
const [price, size] = item.attributes;
|
const [price, size] = item.attributes;
|
||||||
const { preview: image } = item.titlePicture;
|
const image = item?.titlePicture?.preview ?? null;
|
||||||
return {
|
return {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
price: price?.value,
|
price: price?.value,
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export function parse(crawlContainer, crawlFields, text, url) {
|
|||||||
if (fieldSelector.includes('|')) {
|
if (fieldSelector.includes('|')) {
|
||||||
/* eslint-disable no-unused-vars */
|
/* eslint-disable no-unused-vars */
|
||||||
const [_, ...modifiers] = fieldSelector.split('|').map((s) => s.trim());
|
const [_, ...modifiers] = fieldSelector.split('|').map((s) => s.trim());
|
||||||
/* eslint-disable no-unused-vars */
|
/* eslint-enable no-unused-vars */
|
||||||
value = applyModifiers(value, modifiers);
|
value = applyModifiers(value, modifiers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
38
package.json
38
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "fredy",
|
"name": "fredy",
|
||||||
"version": "11.4.2",
|
"version": "11.5.0",
|
||||||
"description": "[F]ind [R]eal [E]states [d]amn eas[y].",
|
"description": "[F]ind [R]eal [E]states [d]amn eas[y].",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepare": "husky",
|
"prepare": "husky",
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
"build:frontend": "vite build",
|
"build:frontend": "vite build",
|
||||||
"format": "prettier --write \"**/*.js\"",
|
"format": "prettier --write \"**/*.js\"",
|
||||||
"format:check": "prettier --check \"**/*.js\"",
|
"format:check": "prettier --check \"**/*.js\"",
|
||||||
"test": "mocha --loader=esmock --timeout 3000000 test/**/*.test.js",
|
"test": "mocha --loader=esmock --timeout 60000 test/**/*.test.js",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"lint:fix": "yarn lint --fix"
|
"lint:fix": "yarn lint --fix"
|
||||||
},
|
},
|
||||||
@@ -54,43 +54,43 @@
|
|||||||
"Firefox ESR"
|
"Firefox ESR"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@douyinfe/semi-ui": "2.85.0",
|
"@douyinfe/semi-ui": "2.86.0",
|
||||||
"@rematch/core": "2.2.0",
|
"@rematch/core": "2.2.0",
|
||||||
"@rematch/loading": "2.1.2",
|
"@rematch/loading": "2.1.2",
|
||||||
"@sendgrid/mail": "8.1.5",
|
"@sendgrid/mail": "8.1.5",
|
||||||
"@vitejs/plugin-react": "4.7.0",
|
"@visactor/react-vchart": "^2.0.4",
|
||||||
"better-sqlite3": "^11.10.0",
|
"@visactor/vchart-semi-theme": "^1.12.2",
|
||||||
|
"@vitejs/plugin-react": "5.0.2",
|
||||||
|
"better-sqlite3": "^12.2.0",
|
||||||
"body-parser": "2.2.0",
|
"body-parser": "2.2.0",
|
||||||
"cheerio": "^1.1.2",
|
"cheerio": "^1.1.2",
|
||||||
"cookie-session": "2.1.1",
|
"cookie-session": "2.1.1",
|
||||||
"handlebars": "4.7.8",
|
"handlebars": "4.7.8",
|
||||||
"highcharts": "12.3.0",
|
|
||||||
"highcharts-react-official": "3.2.2",
|
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"lowdb": "6.0.1",
|
"lowdb": "7.0.1",
|
||||||
"markdown": "^0.5.0",
|
"markdown": "^0.5.0",
|
||||||
"mixpanel": "^0.18.1",
|
"mixpanel": "^0.18.1",
|
||||||
"nanoid": "5.1.5",
|
"nanoid": "5.1.5",
|
||||||
"node-fetch": "3.3.2",
|
"node-fetch": "3.3.2",
|
||||||
"node-mailjet": "6.0.9",
|
"node-mailjet": "6.0.9",
|
||||||
"p-throttle": "^7.0.0",
|
"p-throttle": "^8.0.0",
|
||||||
"package-up": "^5.0.0",
|
"package-up": "^5.0.0",
|
||||||
"puppeteer": "^24.17.0",
|
"puppeteer": "^24.18.0",
|
||||||
"puppeteer-extra": "^3.3.6",
|
"puppeteer-extra": "^3.3.6",
|
||||||
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
||||||
"query-string": "9.2.2",
|
"query-string": "9.2.2",
|
||||||
"react": "18.3.1",
|
"react": "18.3.1",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "18.3.1",
|
||||||
"react-redux": "9.2.0",
|
"react-redux": "9.2.0",
|
||||||
"react-router": "5.2.1",
|
"react-router": "7.8.2",
|
||||||
"react-router-dom": "5.3.0",
|
"react-router-dom": "7.8.2",
|
||||||
"redux": "5.0.1",
|
"redux": "5.0.1",
|
||||||
"redux-thunk": "3.1.0",
|
"redux-thunk": "3.1.0",
|
||||||
"restana": "5.1.0",
|
"restana": "5.1.0",
|
||||||
"serve-static": "2.2.0",
|
"serve-static": "2.2.0",
|
||||||
"slack": "11.0.2",
|
"slack": "11.0.2",
|
||||||
"string-similarity": "^4.0.4",
|
"string-similarity": "^4.0.4",
|
||||||
"vite": "7.1.3",
|
"vite": "7.1.4",
|
||||||
"x-var": "^2.1.0"
|
"x-var": "^2.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -98,16 +98,16 @@
|
|||||||
"@babel/eslint-parser": "7.28.0",
|
"@babel/eslint-parser": "7.28.0",
|
||||||
"@babel/preset-env": "7.28.3",
|
"@babel/preset-env": "7.28.3",
|
||||||
"@babel/preset-react": "7.27.1",
|
"@babel/preset-react": "7.27.1",
|
||||||
"chai": "5.2.1",
|
"chai": "6.0.1",
|
||||||
"eslint": "8.56.0",
|
"eslint": "9.34.0",
|
||||||
"eslint-config-prettier": "8.8.0",
|
"eslint-config-prettier": "10.1.8",
|
||||||
"eslint-plugin-react": "7.37.5",
|
"eslint-plugin-react": "7.37.5",
|
||||||
"esmock": "2.7.1",
|
"esmock": "2.7.2",
|
||||||
"history": "5.3.0",
|
"history": "5.3.0",
|
||||||
"husky": "9.1.7",
|
"husky": "9.1.7",
|
||||||
"less": "4.4.1",
|
"less": "4.4.1",
|
||||||
"lint-staged": "15.5.2",
|
"lint-staged": "16.1.6",
|
||||||
"mocha": "10.8.2",
|
"mocha": "11.7.2",
|
||||||
"nodemon": "^3.1.10",
|
"nodemon": "^3.1.10",
|
||||||
"prettier": "3.6.2",
|
"prettier": "3.6.2",
|
||||||
"redux-logger": "3.0.6"
|
"redux-logger": "3.0.6"
|
||||||
|
|||||||
@@ -7,14 +7,13 @@ import JobMutation from './views/jobs/mutation/JobMutation';
|
|||||||
import UserMutator from './views/user/mutation/UserMutator';
|
import UserMutator from './views/user/mutation/UserMutator';
|
||||||
import JobInsight from './views/jobs/insights/JobInsight.jsx';
|
import JobInsight from './views/jobs/insights/JobInsight.jsx';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { Switch, Redirect } from 'react-router-dom';
|
import { Routes, Route, Navigate } from 'react-router-dom';
|
||||||
import Logout from './components/logout/Logout';
|
import Logout from './components/logout/Logout';
|
||||||
import Logo from './components/logo/Logo';
|
import Logo from './components/logo/Logo';
|
||||||
import Menu from './components/menu/Menu';
|
import Menu from './components/menu/Menu';
|
||||||
import Login from './views/login/Login';
|
import Login from './views/login/Login';
|
||||||
import Users from './views/user/Users';
|
import Users from './views/user/Users';
|
||||||
import Jobs from './views/jobs/Jobs';
|
import Jobs from './views/jobs/Jobs';
|
||||||
import { Route } from 'react-router';
|
|
||||||
|
|
||||||
import './App.less';
|
import './App.less';
|
||||||
import TrackingModal from './components/tracking/TrackingModal.jsx';
|
import TrackingModal from './components/tracking/TrackingModal.jsx';
|
||||||
@@ -49,10 +48,10 @@ export default function FredyApp() {
|
|||||||
const isAdmin = () => currentUser != null && currentUser.isAdmin;
|
const isAdmin = () => currentUser != null && currentUser.isAdmin;
|
||||||
|
|
||||||
const login = () => (
|
const login = () => (
|
||||||
<Switch>
|
<Routes>
|
||||||
<Route name="Login" path={'/login'} component={Login} />
|
<Route path="/login" element={<Login />} />
|
||||||
<Redirect from="*" to={'/login'} />
|
<Route path="*" element={<Navigate to="/login" replace />} />
|
||||||
</Switch>
|
</Routes>
|
||||||
);
|
);
|
||||||
|
|
||||||
return loading ? null : needsLogin() ? (
|
return loading ? null : needsLogin() ? (
|
||||||
@@ -77,34 +76,49 @@ export default function FredyApp() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{settings.analyticsEnabled === null && !settings.demoMode && <TrackingModal />}
|
{settings.analyticsEnabled === null && !settings.demoMode && <TrackingModal />}
|
||||||
<Switch>
|
<Routes>
|
||||||
<Route name="Insufficient Permission" path={'/403'} component={InsufficientPermission} />
|
<Route path="/403" element={<InsufficientPermission />} />
|
||||||
<Route name="Create new Job" path={'/jobs/new'} component={JobMutation} />
|
<Route path="/jobs/new" element={<JobMutation />} />
|
||||||
<Route name="Edit a Job" path={'/jobs/edit/:jobId'} component={JobMutation} />
|
<Route path="/jobs/edit/:jobId" element={<JobMutation />} />
|
||||||
<Route name="Insights into a Job" path={'/jobs/insights/:jobId'} component={JobInsight} />
|
<Route path="/jobs/insights/:jobId" element={<JobInsight />} />
|
||||||
<Route name="Job overview" path={'/jobs'} component={Jobs} />
|
<Route path="/jobs" element={<Jobs />} />
|
||||||
<PermissionAwareRoute
|
|
||||||
name="Create new User"
|
{/* Permission-aware routes */}
|
||||||
|
<Route
|
||||||
path="/users/new"
|
path="/users/new"
|
||||||
component={<UserMutator />}
|
element={
|
||||||
currentUser={currentUser}
|
<PermissionAwareRoute currentUser={currentUser}>
|
||||||
|
<UserMutator />
|
||||||
|
</PermissionAwareRoute>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<PermissionAwareRoute
|
<Route
|
||||||
name="Edit a user"
|
|
||||||
path="/users/edit/:userId"
|
path="/users/edit/:userId"
|
||||||
component={<UserMutator />}
|
element={
|
||||||
currentUser={currentUser}
|
<PermissionAwareRoute currentUser={currentUser}>
|
||||||
|
<UserMutator />
|
||||||
|
</PermissionAwareRoute>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<PermissionAwareRoute name="Users" path="/users" component={<Users />} currentUser={currentUser} />
|
<Route
|
||||||
<PermissionAwareRoute
|
path="/users"
|
||||||
name="General Settings"
|
element={
|
||||||
|
<PermissionAwareRoute currentUser={currentUser}>
|
||||||
|
<Users />
|
||||||
|
</PermissionAwareRoute>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
path="/generalSettings"
|
path="/generalSettings"
|
||||||
component={<GeneralSettings />}
|
element={
|
||||||
currentUser={currentUser}
|
<PermissionAwareRoute currentUser={currentUser}>
|
||||||
|
<GeneralSettings />
|
||||||
|
</PermissionAwareRoute>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Redirect from="/" to={'/jobs'} />
|
<Route path="/" element={<Navigate to="/jobs" replace />} />
|
||||||
</Switch>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,23 +2,24 @@ import React from 'react';
|
|||||||
|
|
||||||
import { reduxStore } from './services/rematch/store';
|
import { reduxStore } from './services/rematch/store';
|
||||||
import { HashRouter } from 'react-router-dom';
|
import { HashRouter } from 'react-router-dom';
|
||||||
import { createHashHistory } from 'history';
|
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
import en_US from '@douyinfe/semi-ui/lib/es/locale/source/en_US';
|
import en_US from '@douyinfe/semi-ui/lib/es/locale/source/en_US';
|
||||||
import { LocaleProvider } from '@douyinfe/semi-ui';
|
import { LocaleProvider } from '@douyinfe/semi-ui';
|
||||||
|
import { initVChartSemiTheme } from '@visactor/vchart-semi-theme';
|
||||||
|
import App from './App';
|
||||||
|
import './Index.less';
|
||||||
|
|
||||||
const container = document.getElementById('fredy');
|
const container = document.getElementById('fredy');
|
||||||
const root = createRoot(container);
|
const root = createRoot(container);
|
||||||
const history = createHashHistory();
|
|
||||||
|
|
||||||
import App from './App';
|
initVChartSemiTheme({
|
||||||
|
defaultMode: 'dark',
|
||||||
import './Index.less';
|
});
|
||||||
|
|
||||||
root.render(
|
root.render(
|
||||||
<Provider store={reduxStore}>
|
<Provider store={reduxStore}>
|
||||||
<HashRouter history={history}>
|
<HashRouter>
|
||||||
<LocaleProvider locale={en_US}>
|
<LocaleProvider locale={en_US}>
|
||||||
<App />
|
<App />
|
||||||
</LocaleProvider>
|
</LocaleProvider>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Tabs, TabPane } from '@douyinfe/semi-ui';
|
import { Tabs, TabPane } from '@douyinfe/semi-ui';
|
||||||
|
|
||||||
import { useLocation } from 'react-router';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { IconUser, IconTerminal, IconSetting } from '@douyinfe/semi-icons';
|
import { IconUser, IconTerminal, IconSetting } from '@douyinfe/semi-icons';
|
||||||
import './Menu.less';
|
import './Menu.less';
|
||||||
|
|
||||||
@@ -12,15 +12,10 @@ function parsePathName(name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TopMenu = function TopMenu({ isAdmin }) {
|
const TopMenu = function TopMenu({ isAdmin }) {
|
||||||
const history = useHistory();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
return (
|
return (
|
||||||
<Tabs
|
<Tabs className="menu" type="line" activeKey={parsePathName(location.pathname)} onTabClick={(key) => navigate(key)}>
|
||||||
className="menu"
|
|
||||||
type="line"
|
|
||||||
activeKey={parsePathName(location.pathname)}
|
|
||||||
onTabClick={(key) => history.push(key)}
|
|
||||||
>
|
|
||||||
<TabPane
|
<TabPane
|
||||||
itemKey="/jobs"
|
itemKey="/jobs"
|
||||||
tab={
|
tab={
|
||||||
|
|||||||
@@ -1,21 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { Redirect } from 'react-router-dom';
|
import { Navigate } from 'react-router-dom';
|
||||||
import { Route } from 'react-router';
|
|
||||||
|
|
||||||
export default function PermissionAwareRoute({ currentUser, name, path, component }) {
|
export default function PermissionAwareRoute({ currentUser, children }) {
|
||||||
/**
|
const isAdmin = currentUser != null && currentUser.isAdmin;
|
||||||
* Checks if given component should be rendered if current user has given permission enabled. If that's not the case,
|
return isAdmin ? children : <Navigate to="/403" replace />;
|
||||||
* user is redirected to '/403'.
|
|
||||||
*
|
|
||||||
* @param permission
|
|
||||||
* @param component
|
|
||||||
* @param path
|
|
||||||
* @returns {*}
|
|
||||||
*/
|
|
||||||
const checkIfAdmin = (component, path) => {
|
|
||||||
return currentUser != null && currentUser.isAdmin ? component : <Redirect from={path} to="/403" />;
|
|
||||||
};
|
|
||||||
|
|
||||||
return <Route name={name} path={path} render={() => checkIfAdmin(component, path)} />;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { demoMode } from './models/demoMode.js';
|
|||||||
import { init } from '@rematch/core';
|
import { init } from '@rematch/core';
|
||||||
const middleware = [];
|
const middleware = [];
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
// eslint-disable-line no-redeclare
|
|
||||||
middleware.push(createLogger({ duration: false, collapsed: (getState, action, logEntry) => !logEntry.error }));
|
middleware.push(createLogger({ duration: false, collapsed: (getState, action, logEntry) => !logEntry.error }));
|
||||||
}
|
}
|
||||||
const store = init({
|
const store = init({
|
||||||
|
|||||||
@@ -8,4 +8,4 @@ export function format(ts) {
|
|||||||
second: 'numeric',
|
second: 'numeric',
|
||||||
}).format(ts);
|
}).format(ts);
|
||||||
}
|
}
|
||||||
export const roundToNext5Minute = (ts) => Math.ceil(ts / (1000 * 60 * 5)) * (1000 * 60 * 5);
|
export const roundToHour = (ts) => Math.ceil(ts / (1000 * 60 * 60)) * (1000 * 60 * 60);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import React from 'react';
|
|||||||
import JobTable from '../../components/table/JobTable';
|
import JobTable from '../../components/table/JobTable';
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import { xhrDelete, xhrPut } from '../../services/xhr';
|
import { xhrDelete, xhrPut } from '../../services/xhr';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import ProcessingTimes from './ProcessingTimes';
|
import ProcessingTimes from './ProcessingTimes';
|
||||||
import { Button, Toast } from '@douyinfe/semi-ui';
|
import { Button, Toast } from '@douyinfe/semi-ui';
|
||||||
import { IconPlusCircle } from '@douyinfe/semi-icons';
|
import { IconPlusCircle } from '@douyinfe/semi-icons';
|
||||||
@@ -12,7 +12,7 @@ import './Jobs.less';
|
|||||||
export default function Jobs() {
|
export default function Jobs() {
|
||||||
const jobs = useSelector((state) => state.jobs.jobs);
|
const jobs = useSelector((state) => state.jobs.jobs);
|
||||||
const processingTimes = useSelector((state) => state.jobs.processingTimes);
|
const processingTimes = useSelector((state) => state.jobs.processingTimes);
|
||||||
const history = useHistory();
|
const navigate = useNavigate();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const onJobRemoval = async (jobId) => {
|
const onJobRemoval = async (jobId) => {
|
||||||
@@ -43,7 +43,7 @@ export default function Jobs() {
|
|||||||
type="primary"
|
type="primary"
|
||||||
icon={<IconPlusCircle />}
|
icon={<IconPlusCircle />}
|
||||||
className="jobs__newButton"
|
className="jobs__newButton"
|
||||||
onClick={() => history.push('/jobs/new')}
|
onClick={() => navigate('/jobs/new')}
|
||||||
>
|
>
|
||||||
New Job
|
New Job
|
||||||
</Button>
|
</Button>
|
||||||
@@ -53,8 +53,8 @@ export default function Jobs() {
|
|||||||
jobs={jobs || []}
|
jobs={jobs || []}
|
||||||
onJobRemoval={onJobRemoval}
|
onJobRemoval={onJobRemoval}
|
||||||
onJobStatusChanged={onJobStatusChanged}
|
onJobStatusChanged={onJobStatusChanged}
|
||||||
onJobInsight={(jobId) => history.push(`/jobs/insights/${jobId}`)}
|
onJobInsight={(jobId) => navigate(`/jobs/insights/${jobId}`)}
|
||||||
onJobEdit={(jobId) => history.push(`/jobs/edit/${jobId}`)}
|
onJobEdit={(jobId) => navigate(`/jobs/edit/${jobId}`)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { roundToNext5Minute } from '../../../services/time/timeService';
|
import { roundToHour } from '../../../services/time/timeService';
|
||||||
import Headline from '../../../components/headline/Headline';
|
import Headline from '../../../components/headline/Headline';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { useParams } from 'react-router';
|
import { useParams } from 'react-router-dom';
|
||||||
import Linechart from './Linechart';
|
import Linechart from './Linechart';
|
||||||
|
|
||||||
const JobInsight = function JobInsight() {
|
const JobInsight = function JobInsight() {
|
||||||
@@ -20,27 +20,47 @@ const JobInsight = function JobInsight() {
|
|||||||
|
|
||||||
const getData = () => {
|
const getData = () => {
|
||||||
const data = insights[params.jobId] || {};
|
const data = insights[params.jobId] || {};
|
||||||
|
const providers = Object.keys(data);
|
||||||
|
|
||||||
const result = [];
|
const countsByProvider = {};
|
||||||
Object.keys(data).forEach((key) => {
|
const allTimes = new Set();
|
||||||
const series = {
|
|
||||||
name: key[0].toUpperCase() + key.substring(1),
|
|
||||||
data: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
|
const cap = (s) => (s ? s[0].toUpperCase() + s.slice(1) : 'Unknown');
|
||||||
|
|
||||||
|
providers.forEach((key) => {
|
||||||
|
const providerName = cap(key);
|
||||||
const tmpTimeObj = {};
|
const tmpTimeObj = {};
|
||||||
|
|
||||||
Object.values(data[key] || {}).forEach((listingTs) => {
|
Object.values(data[key] || {}).forEach((listingTs) => {
|
||||||
const time = roundToNext5Minute(listingTs);
|
const time = roundToHour(listingTs);
|
||||||
tmpTimeObj[time] = tmpTimeObj[time] == null ? 1 : tmpTimeObj[time] + 1;
|
tmpTimeObj[time] = tmpTimeObj[time] == null ? 1 : tmpTimeObj[time] + 1;
|
||||||
|
allTimes.add(time);
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.keys(tmpTimeObj)
|
countsByProvider[providerName] = tmpTimeObj;
|
||||||
.sort()
|
});
|
||||||
.forEach((timeKey) => {
|
|
||||||
series.data.push([parseInt(timeKey), tmpTimeObj[timeKey]]);
|
const sortedTimes = Array.from(allTimes).sort((a, b) => a - b);
|
||||||
|
|
||||||
|
const result = [];
|
||||||
|
providers.forEach((key) => {
|
||||||
|
const providerName = cap(key);
|
||||||
|
const bucket = countsByProvider[providerName] || {};
|
||||||
|
|
||||||
|
sortedTimes.forEach((t) => {
|
||||||
|
result.push({
|
||||||
|
listings: new Intl.DateTimeFormat('en-US', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
hour12: false,
|
||||||
|
}).format(new Date(parseInt(t))),
|
||||||
|
listingsNumber: bucket[t] || 0, // y value
|
||||||
|
provider: providerName, // series key
|
||||||
});
|
});
|
||||||
result.push(series);
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -1,337 +1,49 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import Placeholder from '../../../components/placeholder/Placeholder';
|
import Placeholder from '../../../components/placeholder/Placeholder';
|
||||||
import HighchartsReact from 'highcharts-react-official';
|
import { VChart } from '@visactor/react-vchart';
|
||||||
import Highcharts from 'highcharts/highcharts.src.js';
|
|
||||||
|
|
||||||
import './Linechart.less';
|
import './Linechart.less';
|
||||||
|
|
||||||
Highcharts.theme = {
|
const commonSpec = {
|
||||||
colors: [
|
type: 'line',
|
||||||
'#2b908f',
|
xField: 'listings',
|
||||||
'#90ee7e',
|
yField: 'listingsNumber',
|
||||||
'#f45b5b',
|
seriesField: 'provider',
|
||||||
'#7798BF',
|
legends: { visible: true },
|
||||||
'#aaeeee',
|
line: {
|
||||||
'#ff0066',
|
style: {
|
||||||
'#eeaaee',
|
lineWidth: 2,
|
||||||
'#55BF3B',
|
},
|
||||||
'#DF5353',
|
},
|
||||||
'#7798BF',
|
point: {
|
||||||
'#aaeeee',
|
visible: false,
|
||||||
|
},
|
||||||
|
axes: [
|
||||||
|
{
|
||||||
|
orient: 'bottom',
|
||||||
|
field: 'listings',
|
||||||
|
zero: false,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
chart: {
|
|
||||||
backgroundColor: {
|
|
||||||
linearGradient: {
|
|
||||||
x1: 0,
|
|
||||||
y1: 0,
|
|
||||||
x2: 1,
|
|
||||||
y2: 1,
|
|
||||||
},
|
|
||||||
stops: [
|
|
||||||
[0, '#2a2a2b'],
|
|
||||||
[1, '#3e3e40'],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
style: {
|
|
||||||
fontFamily: "'Unica One', sans-serif",
|
|
||||||
},
|
|
||||||
plotBorderColor: '#606063',
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
style: {
|
|
||||||
color: '#E0E0E3',
|
|
||||||
textTransform: 'uppercase',
|
|
||||||
fontSize: '20px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
subtitle: {
|
|
||||||
style: {
|
|
||||||
color: '#E0E0E3',
|
|
||||||
textTransform: 'uppercase',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
xAxis: {
|
|
||||||
gridLineColor: '#707073',
|
|
||||||
labels: {
|
|
||||||
style: {
|
|
||||||
color: '#E0E0E3',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lineColor: '#707073',
|
|
||||||
minorGridLineColor: '#505053',
|
|
||||||
tickColor: '#707073',
|
|
||||||
title: {
|
|
||||||
style: {
|
|
||||||
color: '#A0A0A3',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
gridLineColor: '#707073',
|
|
||||||
labels: {
|
|
||||||
style: {
|
|
||||||
color: '#E0E0E3',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lineColor: '#707073',
|
|
||||||
minorGridLineColor: '#505053',
|
|
||||||
tickColor: '#707073',
|
|
||||||
tickWidth: 1,
|
|
||||||
title: {
|
|
||||||
style: {
|
|
||||||
color: '#A0A0A3',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
backgroundColor: 'rgba(0, 0, 0, 0.85)',
|
|
||||||
style: {
|
|
||||||
color: '#F0F0F0',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plotOptions: {
|
|
||||||
series: {
|
|
||||||
dataLabels: {
|
|
||||||
color: '#F0F0F3',
|
|
||||||
style: {
|
|
||||||
fontSize: '13px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
marker: {
|
|
||||||
lineColor: '#333',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
boxplot: {
|
|
||||||
fillColor: '#505053',
|
|
||||||
},
|
|
||||||
candlestick: {
|
|
||||||
lineColor: 'white',
|
|
||||||
},
|
|
||||||
errorbar: {
|
|
||||||
color: 'white',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
||||||
itemStyle: {
|
|
||||||
color: '#E0E0E3',
|
|
||||||
},
|
|
||||||
itemHoverStyle: {
|
|
||||||
color: '#FFF',
|
|
||||||
},
|
|
||||||
itemHiddenStyle: {
|
|
||||||
color: '#606063',
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
style: {
|
|
||||||
color: '#C0C0C0',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
credits: {
|
|
||||||
style: {
|
|
||||||
color: '#666',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
labels: {
|
|
||||||
style: {
|
|
||||||
color: '#707073',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
drilldown: {
|
|
||||||
activeAxisLabelStyle: {
|
|
||||||
color: '#F0F0F3',
|
|
||||||
},
|
|
||||||
activeDataLabelStyle: {
|
|
||||||
color: '#F0F0F3',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
navigation: {
|
|
||||||
buttonOptions: {
|
|
||||||
symbolStroke: '#DDDDDD',
|
|
||||||
theme: {
|
|
||||||
fill: '#505053',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// scroll charts
|
|
||||||
rangeSelector: {
|
|
||||||
buttonTheme: {
|
|
||||||
fill: '#505053',
|
|
||||||
stroke: '#000000',
|
|
||||||
style: {
|
|
||||||
color: '#CCC',
|
|
||||||
},
|
|
||||||
states: {
|
|
||||||
hover: {
|
|
||||||
fill: '#707073',
|
|
||||||
stroke: '#000000',
|
|
||||||
style: {
|
|
||||||
color: 'white',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
fill: '#000003',
|
|
||||||
stroke: '#000000',
|
|
||||||
style: {
|
|
||||||
color: 'white',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
inputBoxBorderColor: '#505053',
|
|
||||||
inputStyle: {
|
|
||||||
backgroundColor: '#333',
|
|
||||||
color: 'silver',
|
|
||||||
},
|
|
||||||
labelStyle: {
|
|
||||||
color: 'silver',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
navigator: {
|
|
||||||
handles: {
|
|
||||||
backgroundColor: '#666',
|
|
||||||
borderColor: '#AAA',
|
|
||||||
},
|
|
||||||
outlineColor: '#CCC',
|
|
||||||
maskFill: 'rgba(255,255,255,0.1)',
|
|
||||||
series: {
|
|
||||||
color: '#7798BF',
|
|
||||||
lineColor: '#A6C7ED',
|
|
||||||
},
|
|
||||||
xAxis: {
|
|
||||||
gridLineColor: '#505053',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
scrollbar: {
|
|
||||||
barBackgroundColor: '#808083',
|
|
||||||
barBorderColor: '#808083',
|
|
||||||
buttonArrowColor: '#CCC',
|
|
||||||
buttonBackgroundColor: '#606063',
|
|
||||||
buttonBorderColor: '#606063',
|
|
||||||
rifleColor: '#FFF',
|
|
||||||
trackBackgroundColor: '#404043',
|
|
||||||
trackBorderColor: '#404043',
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Apply the theme
|
const Linechart = function Linechart({ title, series, isLoading = false }) {
|
||||||
Highcharts.setOptions(Highcharts.theme);
|
|
||||||
|
|
||||||
const defaultOptions = {
|
|
||||||
title: {
|
|
||||||
text: null,
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
xAxis: {
|
|
||||||
//most of the time (if not everytime), the x axis is time
|
|
||||||
type: 'datetime',
|
|
||||||
crosshair: {
|
|
||||||
snap: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
title: {
|
|
||||||
text: null,
|
|
||||||
},
|
|
||||||
//do not show float numbers
|
|
||||||
allowDecimals: false,
|
|
||||||
},
|
|
||||||
chart: {
|
|
||||||
type: 'line',
|
|
||||||
zoomType: 'x',
|
|
||||||
plotBackgroundColor: null,
|
|
||||||
plotBorderWidth: null,
|
|
||||||
},
|
|
||||||
exporting: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
shared: true,
|
|
||||||
formatter: null,
|
|
||||||
},
|
|
||||||
plotOptions: {
|
|
||||||
line: {
|
|
||||||
animation: false,
|
|
||||||
marker: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
series: {
|
|
||||||
lineWidth: 1.5,
|
|
||||||
connectNulls: true,
|
|
||||||
marker: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
series: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Usage of this chart:
|
|
||||||
* title: optional (show a title, if null, no title is shown)
|
|
||||||
* zoom: optional (if true, zooming in x axis is possible)
|
|
||||||
* legend: optional (show / hide the legend)
|
|
||||||
* series: mandatory (an array of data to be shown)
|
|
||||||
*
|
|
||||||
* <Linechart
|
|
||||||
* title="something"
|
|
||||||
* legend={true}
|
|
||||||
* timeframe={week/month/all} --> If this is not set, we assume the timeframe is 'all'
|
|
||||||
* //everything that is "subscribed" to this topic will receive this update
|
|
||||||
* highlightTopic="someTopic"
|
|
||||||
* height={"500px"}
|
|
||||||
* zoom={true}
|
|
||||||
* series={[
|
|
||||||
* {
|
|
||||||
* name: 'something',
|
|
||||||
* data: [x,y],
|
|
||||||
* dashStyle: (OPTIONAL) | solid / 'shortdot'
|
|
||||||
* }
|
|
||||||
* ]}
|
|
||||||
* />
|
|
||||||
*/
|
|
||||||
const Linechart = function Linechart({ title, series, height, isLoading = false }) {
|
|
||||||
const options = () => {
|
|
||||||
return {
|
|
||||||
...defaultOptions,
|
|
||||||
title: {
|
|
||||||
text: title,
|
|
||||||
},
|
|
||||||
time: {
|
|
||||||
useUTC: false,
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
series: series.map((series) => {
|
|
||||||
return {
|
|
||||||
...series,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
chart: {
|
|
||||||
type: 'line',
|
|
||||||
zoomType: 'x',
|
|
||||||
height: height || '400px',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Placeholder ready={!isLoading} rows={6}>
|
<Placeholder ready={!isLoading} rows={6}>
|
||||||
{series == null || series.length === 0 ? (
|
{series == null || series.length === 0 ? (
|
||||||
<div className="linechart__no__data">No Data for selected timeframe :-/</div>
|
<div className="linechart__no__data">No Data for selected timeframe :-/</div>
|
||||||
) : (
|
) : (
|
||||||
<HighchartsReact highcharts={Highcharts} options={options()} />
|
<VChart
|
||||||
|
spec={{
|
||||||
|
...commonSpec,
|
||||||
|
title: {
|
||||||
|
visible: true,
|
||||||
|
text: title,
|
||||||
|
},
|
||||||
|
data: { values: series },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</Placeholder>
|
</Placeholder>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ import ProviderMutator from './components/provider/ProviderMutator';
|
|||||||
import Headline from '../../../components/headline/Headline';
|
import Headline from '../../../components/headline/Headline';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { xhrPost } from '../../../services/xhr';
|
import { xhrPost } from '../../../services/xhr';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import { useParams } from 'react-router';
|
|
||||||
import { Divider, Input, Switch, Button, TagInput, Toast } from '@douyinfe/semi-ui';
|
import { Divider, Input, Switch, Button, TagInput, Toast } from '@douyinfe/semi-ui';
|
||||||
import './JobMutation.less';
|
import './JobMutation.less';
|
||||||
import { SegmentPart } from '../../../components/segment/SegmentPart';
|
import { SegmentPart } from '../../../components/segment/SegmentPart';
|
||||||
@@ -34,7 +33,7 @@ export default function JobMutator() {
|
|||||||
const [blacklist, setBlacklist] = useState(defaultBlacklist);
|
const [blacklist, setBlacklist] = useState(defaultBlacklist);
|
||||||
const [notificationAdapterData, setNotificationAdapterData] = useState(defaultNotificationAdapter);
|
const [notificationAdapterData, setNotificationAdapterData] = useState(defaultNotificationAdapter);
|
||||||
const [enabled, setEnabled] = useState(defaultEnabled);
|
const [enabled, setEnabled] = useState(defaultEnabled);
|
||||||
const history = useHistory();
|
const navigate = useNavigate();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const isSavingEnabled = () => {
|
const isSavingEnabled = () => {
|
||||||
@@ -53,7 +52,7 @@ export default function JobMutator() {
|
|||||||
});
|
});
|
||||||
await dispatch.jobs.getJobs();
|
await dispatch.jobs.getJobs();
|
||||||
Toast.success('Job successfully saved...');
|
Toast.success('Job successfully saved...');
|
||||||
history.push('/jobs');
|
navigate('/jobs');
|
||||||
} catch (Exception) {
|
} catch (Exception) {
|
||||||
console.error(Exception.json.message);
|
console.error(Exception.json.message);
|
||||||
Toast.error(Exception.json != null ? Exception.json.message : Exception);
|
Toast.error(Exception.json != null ? Exception.json.message : Exception);
|
||||||
@@ -177,7 +176,7 @@ export default function JobMutator() {
|
|||||||
<Switch className="jobMutation__spaceTop" onChange={(checked) => setEnabled(checked)} checked={enabled} />
|
<Switch className="jobMutation__spaceTop" onChange={(checked) => setEnabled(checked)} checked={enabled} />
|
||||||
</SegmentPart>
|
</SegmentPart>
|
||||||
<Divider margin="1rem" />
|
<Divider margin="1rem" />
|
||||||
<Button type="danger" style={{ marginRight: '1rem' }} onClick={() => history.push('/jobs')}>
|
<Button type="danger" style={{ marginRight: '1rem' }} onClick={() => navigate('/jobs')}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="primary" icon={<IconPlusCircle />} disabled={!isSavingEnabled()} onClick={mutateJob}>
|
<Button type="primary" icon={<IconPlusCircle />} disabled={!isSavingEnabled()} onClick={mutateJob}>
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ export default function ProviderMutator({ onVisibilityChanged, visible = false,
|
|||||||
if (selectedProvider.baseUrl.indexOf(url.origin) === -1) {
|
if (selectedProvider.baseUrl.indexOf(url.origin) === -1) {
|
||||||
return 'The url you have copied is not valid.';
|
return 'The url you have copied is not valid.';
|
||||||
}
|
}
|
||||||
} catch (Exception) {
|
/* eslint-disable no-unused-vars */
|
||||||
|
} catch (ignored) {
|
||||||
return 'The url you have copied is not valid.';
|
return 'The url you have copied is not valid.';
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import React, { useEffect } from 'react';
|
|||||||
import cityBackground from '../../assets/city_background.jpg';
|
import cityBackground from '../../assets/city_background.jpg';
|
||||||
import Logo from '../../components/logo/Logo';
|
import Logo from '../../components/logo/Logo';
|
||||||
import { xhrPost } from '../../services/xhr';
|
import { xhrPost } from '../../services/xhr';
|
||||||
import { useHistory } from 'react-router';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { Input, Button, Banner, Toast } from '@douyinfe/semi-ui';
|
import { Input, Button, Banner, Toast } from '@douyinfe/semi-ui';
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ export default function Login() {
|
|||||||
const [password, setPassword] = React.useState('');
|
const [password, setPassword] = React.useState('');
|
||||||
const [error, setError] = React.useState(null);
|
const [error, setError] = React.useState(null);
|
||||||
const demoMode = useSelector((state) => state.demoMode.demoMode || false);
|
const demoMode = useSelector((state) => state.demoMode.demoMode || false);
|
||||||
const history = useHistory();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function init() {
|
async function init() {
|
||||||
@@ -38,7 +38,8 @@ export default function Login() {
|
|||||||
username: username.trim(),
|
username: username.trim(),
|
||||||
password,
|
password,
|
||||||
});
|
});
|
||||||
} catch (Exception) {
|
/* eslint-disable no-unused-vars */
|
||||||
|
} catch (ignored) {
|
||||||
Toast.error('Login unsuccessful…');
|
Toast.error('Login unsuccessful…');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -46,7 +47,7 @@ export default function Login() {
|
|||||||
Toast.success('Login successful!');
|
Toast.success('Login successful!');
|
||||||
|
|
||||||
await dispatch.user.getCurrentUser();
|
await dispatch.user.getCurrentUser();
|
||||||
history.push('/jobs');
|
navigate('/jobs');
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { IconPlus } from '@douyinfe/semi-icons';
|
|||||||
import { Button } from '@douyinfe/semi-ui';
|
import { Button } from '@douyinfe/semi-ui';
|
||||||
import UserRemovalModal from './UserRemovalModal';
|
import UserRemovalModal from './UserRemovalModal';
|
||||||
import { xhrDelete } from '../../services/xhr';
|
import { xhrDelete } from '../../services/xhr';
|
||||||
import { useHistory } from 'react-router';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import './Users.less';
|
import './Users.less';
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ const Users = function Users() {
|
|||||||
const [loading, setLoading] = React.useState(true);
|
const [loading, setLoading] = React.useState(true);
|
||||||
const users = useSelector((state) => state.user.users);
|
const users = useSelector((state) => state.user.users);
|
||||||
const [userIdToBeRemoved, setUserIdToBeRemoved] = React.useState(null);
|
const [userIdToBeRemoved, setUserIdToBeRemoved] = React.useState(null);
|
||||||
const history = useHistory();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
async function init() {
|
async function init() {
|
||||||
@@ -50,7 +50,7 @@ const Users = function Users() {
|
|||||||
type="primary"
|
type="primary"
|
||||||
className="users__newButton"
|
className="users__newButton"
|
||||||
icon={<IconPlus />}
|
icon={<IconPlus />}
|
||||||
onClick={() => history.push('/users/new')}
|
onClick={() => navigate('/users/new')}
|
||||||
>
|
>
|
||||||
Create new User
|
Create new User
|
||||||
</Button>
|
</Button>
|
||||||
@@ -58,7 +58,7 @@ const Users = function Users() {
|
|||||||
<UserTable
|
<UserTable
|
||||||
user={users}
|
user={users}
|
||||||
onUserEdit={(userId) => {
|
onUserEdit={(userId) => {
|
||||||
history.push(`/users/edit/${userId}`);
|
navigate(`/users/edit/${userId}`);
|
||||||
}}
|
}}
|
||||||
onUserRemoval={(userId) => {
|
onUserRemoval={(userId) => {
|
||||||
setUserIdToBeRemoved(userId);
|
setUserIdToBeRemoved(userId);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { xhrGet, xhrPost } from '../../../services/xhr';
|
import { xhrGet, xhrPost } from '../../../services/xhr';
|
||||||
import { useHistory, useParams } from 'react-router';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { Divider, Input, Switch, Button, Toast } from '@douyinfe/semi-ui';
|
import { Divider, Input, Switch, Button, Toast } from '@douyinfe/semi-ui';
|
||||||
import './UserMutator.less';
|
import './UserMutator.less';
|
||||||
@@ -15,7 +15,7 @@ const UserMutator = function UserMutator() {
|
|||||||
const [password2, setPassword2] = React.useState('');
|
const [password2, setPassword2] = React.useState('');
|
||||||
const [isAdmin, setIsAdmin] = React.useState(false);
|
const [isAdmin, setIsAdmin] = React.useState(false);
|
||||||
|
|
||||||
const history = useHistory();
|
const navigate = useNavigate();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@@ -50,7 +50,7 @@ const UserMutator = function UserMutator() {
|
|||||||
});
|
});
|
||||||
await dispatch.user.getUsers();
|
await dispatch.user.getUsers();
|
||||||
Toast.success('User successfully saved...');
|
Toast.success('User successfully saved...');
|
||||||
history.push('/users');
|
navigate('/users');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
Toast.error(error.json.message);
|
Toast.error(error.json.message);
|
||||||
@@ -98,7 +98,7 @@ const UserMutator = function UserMutator() {
|
|||||||
<Switch checked={isAdmin} onChange={(checked) => setIsAdmin(checked)} />
|
<Switch checked={isAdmin} onChange={(checked) => setIsAdmin(checked)} />
|
||||||
</SegmentPart>
|
</SegmentPart>
|
||||||
<Divider margin="1rem" />
|
<Divider margin="1rem" />
|
||||||
<Button type="danger" style={{ marginRight: '1rem' }} onClick={() => history.push('/users')}>
|
<Button type="danger" style={{ marginRight: '1rem' }} onClick={() => navigate('/users')}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="primary" icon={<IconPlusCircle />} onClick={saveUser}>
|
<Button type="primary" icon={<IconPlusCircle />} onClick={saveUser}>
|
||||||
|
|||||||
Reference in New Issue
Block a user