react-admin-base-bootstrap 0.7.4 → 0.7.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/assets/main.css CHANGED
@@ -7,6 +7,10 @@
7
7
  @import url('~tingle.js/dist/tingle.css');
8
8
  @import url('~cropperjs/dist/cropper.css');
9
9
 
10
+ .password-str-bar ~ .invalid-feedback {
11
+ margin-top: -20px;
12
+ }
13
+
10
14
  .tingle-modal--visible {
11
15
  z-index: 10000;
12
16
  }
@@ -8,7 +8,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
11
- import { DataContextProvider, useAuth, useDataTable } from 'react-admin-base';
11
+ import { DataContextProvider, RefreshScope, useAuth, useDataTable } from 'react-admin-base';
12
12
  import { FormattedMessage, useIntl } from "react-intl";
13
13
  import { Link } from 'react-router-dom';
14
14
  import { Alert, Button, Card, CardFooter, CardHeader, Col, Input, Row, Table } from 'reactstrap';
@@ -67,10 +67,7 @@ export function ActionsColumn() {
67
67
  export function Column(props) {
68
68
  const [params, setParams] = useContext(DataTableContext);
69
69
  const { sort } = props;
70
- return React.createElement("th", Object.assign({}, props, { style: props.sort && { cursor: 'pointer' }, onClick: props.sort && (() => setParams({
71
- sort: sort,
72
- desc: params.sort === sort ? !params.desc : true
73
- })) }),
70
+ return React.createElement("th", Object.assign({}, props, { style: props.sort && { cursor: 'pointer' }, onClick: props.sort && (() => setParams(params => (Object.assign(Object.assign({}, params), { sort: sort, desc: params.sort === sort ? !params.desc : true })))) }),
74
71
  props.children,
75
72
  " ",
76
73
  sort && params.sort && params.sort === sort ? params.desc ?
@@ -80,7 +77,7 @@ export function Column(props) {
80
77
  export default function BootstrapTable({ url, bordered, noStrip, defaultParams, add, children, innerRef, body }) {
81
78
  var state = useState(Object.assign({ sort: 'id' }, defaultParams));
82
79
  const [params, setParams] = state;
83
- const [page, lastPage, setPage, data, itemPerPage, setItemPerPage] = useDataTable(url, params, body);
80
+ const [page, lastPage, setPage, data, itemPerPage, setItemPerPage, update] = useDataTable(url, params, body);
84
81
  const intl = useIntl();
85
82
  const [api] = useAuth();
86
83
  var ref = useRef(defaultParams);
@@ -111,31 +108,32 @@ export default function BootstrapTable({ url, bordered, noStrip, defaultParams,
111
108
  return React.createElement(Card, null,
112
109
  React.createElement(DataTableContext.Provider, { value: state },
113
110
  React.createElement(DataContextProvider, { value: fetchData },
114
- React.createElement(CardHeader, null,
115
- React.createElement(Row, null,
116
- add && React.createElement(Col, { xs: "12", md: "2" },
117
- React.createElement(Link, { to: add, className: "btn btn-primary font-xl d-block" },
118
- React.createElement("i", { className: "fa fa-plus" }))),
119
- React.createElement(Col, { md: "2" },
120
- React.createElement(Input, { type: "select", value: itemPerPage.toString(), onChange: a => setItemPerPage(+a.currentTarget.value) },
121
- React.createElement("option", { value: "1" }, "1"),
122
- React.createElement("option", { value: "20" }, "20"),
123
- React.createElement("option", { value: "50" }, "50"),
124
- React.createElement("option", { value: "100" }, "100"),
125
- React.createElement("option", { value: "150" }, "150"),
126
- React.createElement("option", { value: "200" }, "200"),
127
- React.createElement("option", { value: "-1" }, intl.formatMessage({ id: "ALL" })))),
128
- children[2],
129
- React.createElement(Col, { md: "3", className: "ms-auto" },
130
- React.createElement(Input, { placeholder: intl.formatMessage({ id: "SEARCH" }), type: "text", value: params.query || '', onChange: e => setParams(Object.assign(Object.assign({}, params), { query: e.currentTarget.value })) })))),
131
- data === null ? React.createElement(Alert, { className: "text-center mb-0 mx-3 ", color: "warning" },
132
- React.createElement("i", { className: "fas fa-spinner fa-spin" })) : !data.length ? React.createElement(Alert, { className: "text-center mx-3", color: "danger" },
133
- React.createElement("i", { className: "far fa-times-circle" }),
134
- " ",
135
- React.createElement(FormattedMessage, { id: "NO_DATA_IS_AVAILABLE" })) : React.createElement(Table, { hover: true, bordered: bordered, striped: !noStrip, responsive: true, size: "md", className: "mb-0 dataTable" },
136
- children[0],
137
- React.createElement("tbody", null, data && data.map(children[1].props.children))),
138
- lastPage > 1 && React.createElement(CardFooter, null,
139
- React.createElement("nav", null,
140
- React.createElement(BootstrapPagination, { currentPage: page, pageCount: lastPage, onPageChange: index => setPage(index) }))))));
111
+ React.createElement(RefreshScope, { update: update },
112
+ React.createElement(CardHeader, null,
113
+ React.createElement(Row, null,
114
+ add && React.createElement(Col, { xs: "12", md: "2" },
115
+ React.createElement(Link, { to: add, className: "btn btn-primary font-xl d-block" },
116
+ React.createElement("i", { className: "fa fa-plus" }))),
117
+ React.createElement(Col, { md: "2" },
118
+ React.createElement(Input, { type: "select", value: itemPerPage.toString(), onChange: a => setItemPerPage(+a.currentTarget.value) },
119
+ React.createElement("option", { value: "1" }, "1"),
120
+ React.createElement("option", { value: "20" }, "20"),
121
+ React.createElement("option", { value: "50" }, "50"),
122
+ React.createElement("option", { value: "100" }, "100"),
123
+ React.createElement("option", { value: "150" }, "150"),
124
+ React.createElement("option", { value: "200" }, "200"),
125
+ React.createElement("option", { value: "-1" }, intl.formatMessage({ id: "ALL" })))),
126
+ children[2],
127
+ React.createElement(Col, { md: "3", className: "ms-auto" },
128
+ React.createElement(Input, { placeholder: intl.formatMessage({ id: "SEARCH" }), type: "text", value: params.query || '', onChange: e => setParams(Object.assign(Object.assign({}, params), { query: e.currentTarget.value })) })))),
129
+ data === null ? React.createElement(Alert, { className: "text-center mb-0 mx-3 ", color: "warning" },
130
+ React.createElement("i", { className: "fas fa-spinner fa-spin" })) : !data.length ? React.createElement(Alert, { className: "text-center mx-3", color: "danger" },
131
+ React.createElement("i", { className: "far fa-times-circle" }),
132
+ " ",
133
+ React.createElement(FormattedMessage, { id: "NO_DATA_IS_AVAILABLE" })) : React.createElement(Table, { hover: true, bordered: bordered, striped: !noStrip, responsive: true, size: "md", className: "mb-0 dataTable" },
134
+ children[0],
135
+ React.createElement("tbody", null, data && data.map(children[1].props.children))),
136
+ lastPage > 1 && React.createElement(CardFooter, null,
137
+ React.createElement("nav", null,
138
+ React.createElement(BootstrapPagination, { currentPage: page, pageCount: lastPage, onPageChange: index => setPage(index) })))))));
141
139
  }
@@ -0,0 +1,3 @@
1
+ export default function DefaultValidatorOptions({ children }: {
2
+ children: any;
3
+ }): JSX.Element;
@@ -0,0 +1,24 @@
1
+ import React, { useMemo } from "react";
2
+ import { ValidatorOptionProvider } from "react-admin-base";
3
+ import { useIntl } from "react-intl";
4
+ import zxcvbn from 'zxcvbn';
5
+ export default function DefaultValidatorOptions({ children }) {
6
+ const intl = useIntl();
7
+ const options = useMemo(() => ({
8
+ validators: {
9
+ password: {
10
+ message: intl.formatMessage({ id: 'PASSWORD_NOMATCH' }),
11
+ rule: function (val, params) {
12
+ return val == params[0];
13
+ }
14
+ },
15
+ securepassword: {
16
+ message: intl.formatMessage({ id: 'INSECURE_PASSWORD' }),
17
+ rule: function (val, params) {
18
+ return !val || (zxcvbn(val, []).score >= 2);
19
+ }
20
+ }
21
+ }
22
+ }), [intl]);
23
+ return React.createElement(ValidatorOptionProvider, { value: options }, children);
24
+ }
@@ -1,5 +1,7 @@
1
1
  import React from 'react';
2
- export default class ErrorBoundary extends React.Component {
2
+ export default class ErrorBoundary extends React.Component<{
3
+ children: React.ReactNode;
4
+ }> {
3
5
  state: {
4
6
  hasError: boolean;
5
7
  error: null;
@@ -8,5 +10,5 @@ export default class ErrorBoundary extends React.Component {
8
10
  hasError: boolean;
9
11
  error: any;
10
12
  };
11
- render(): React.ReactNode;
13
+ render(): string | number | boolean | JSX.Element | React.ReactFragment | null | undefined;
12
14
  }
@@ -0,0 +1,11 @@
1
+ interface PasswordInputParams {
2
+ value?: string;
3
+ onChange: (str: string) => any;
4
+ className?: string;
5
+ disabled?: boolean;
6
+ icon?: string;
7
+ required?: boolean;
8
+ placeholder?: string;
9
+ }
10
+ export default function PasswordInput({ value, onChange, className, disabled, icon, required, placeholder }: PasswordInputParams): JSX.Element;
11
+ export {};
@@ -0,0 +1,39 @@
1
+ import React, { useEffect, useMemo, useState } from "react";
2
+ import { FormattedMessage, useIntl } from "react-intl";
3
+ import PasswordStrengthBar from "react-password-strength-bar";
4
+ import { FormGroup, Label, Input } from 'reactstrap';
5
+ import { Validator } from "./Validator";
6
+ function BootstrapPasswordInput({ className, value, onChange, disabled, placeholder }) {
7
+ const intl = useIntl();
8
+ const short = intl.formatMessage({ id: "PASSWORD_SHORT" });
9
+ const bad = intl.formatMessage({ id: "PASSWORD_BAD" });
10
+ const okay = intl.formatMessage({ id: "PASSWORD_OKAY" });
11
+ const good = intl.formatMessage({ id: "PASSWORD_GOOD" });
12
+ const perfect = intl.formatMessage({ id: "PASSWORD_PERFECT" });
13
+ const scoreWords = useMemo(() => [short, bad, okay, good, perfect], [short, bad, okay, good, perfect]);
14
+ return React.createElement(React.Fragment, null,
15
+ React.createElement(Input, { className: className, type: "password", value: value || '', onChange: a => onChange(a.currentTarget.value), disabled: disabled, placeholder: placeholder, autoComplete: "new-password" }),
16
+ React.createElement(PasswordStrengthBar, { className: "password-str-bar", password: value || '', scoreWords: scoreWords, shortScoreWord: short }));
17
+ }
18
+ export default function PasswordInput({ value, onChange, className, disabled, icon, required, placeholder }) {
19
+ const [password2, setPassword2] = useState('');
20
+ const iconElem = icon && React.createElement("i", { className: icon });
21
+ useEffect(function () {
22
+ if (!value) {
23
+ setPassword2('');
24
+ }
25
+ }, [value, setPassword2]);
26
+ return React.createElement(React.Fragment, null,
27
+ React.createElement(FormGroup, { className: className },
28
+ iconElem,
29
+ React.createElement(Label, null,
30
+ React.createElement(FormattedMessage, { id: "PASSWORD" })),
31
+ React.createElement(Validator, { name: "password", type: [required && "required", "securepassword"].filter(a => !!a) },
32
+ React.createElement(BootstrapPasswordInput, { value: value, onChange: onChange, disabled: disabled, placeholder: placeholder }))),
33
+ !!(required || value) && React.createElement(FormGroup, { className: className },
34
+ iconElem,
35
+ React.createElement(Label, null,
36
+ React.createElement(FormattedMessage, { id: "PASSWORD_AGAIN" })),
37
+ React.createElement(Validator, { name: "password2", type: [(required || value) && "required", { password: value || '' }].filter(a => !!a) },
38
+ React.createElement(Input, { type: "password", value: password2 || '', onChange: a => setPassword2(a.currentTarget.value), disabled: disabled, autoComplete: "new-password" }))));
39
+ }
@@ -0,0 +1,4 @@
1
+ export default function RichTextEditor({ value, onChange }: {
2
+ value: any;
3
+ onChange: any;
4
+ }): JSX.Element;
@@ -0,0 +1,52 @@
1
+ import React from 'react';
2
+ import { LexicalComposer } from "@lexical/react/LexicalComposer";
3
+ import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
4
+ import { ContentEditable } from "@lexical/react/LexicalContentEditable";
5
+ import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin";
6
+ import { AutoFocusPlugin } from "@lexical/react/LexicalAutoFocusPlugin";
7
+ import { HeadingNode, QuoteNode } from "@lexical/rich-text";
8
+ import { TableCellNode, TableNode, TableRowNode } from "@lexical/table";
9
+ import { ListItemNode, ListNode } from "@lexical/list";
10
+ import { CodeHighlightNode, CodeNode } from "@lexical/code";
11
+ import { AutoLinkNode, LinkNode } from "@lexical/link";
12
+ import { LinkPlugin } from "@lexical/react/LexicalLinkPlugin";
13
+ import { ListPlugin } from "@lexical/react/LexicalListPlugin";
14
+ import { MarkdownShortcutPlugin } from "@lexical/react/LexicalMarkdownShortcutPlugin";
15
+ import { TRANSFORMERS } from "@lexical/markdown";
16
+ import ToolbarPlugin from '../Lexical/ToolbarPlugin';
17
+ function Placeholder() {
18
+ return React.createElement("div", { className: "editor-placeholder" }, "Enter some rich text...");
19
+ }
20
+ const editorConfig = {
21
+ namespace: '',
22
+ // Handling of errors during update
23
+ onError(error) {
24
+ throw error;
25
+ },
26
+ // Any custom nodes go here
27
+ nodes: [
28
+ HeadingNode,
29
+ ListNode,
30
+ ListItemNode,
31
+ QuoteNode,
32
+ CodeNode,
33
+ CodeHighlightNode,
34
+ TableNode,
35
+ TableCellNode,
36
+ TableRowNode,
37
+ AutoLinkNode,
38
+ LinkNode
39
+ ]
40
+ };
41
+ export default function RichTextEditor({ value, onChange }) {
42
+ return React.createElement(LexicalComposer, { initialConfig: editorConfig },
43
+ React.createElement("div", { className: "editor-container" },
44
+ React.createElement(ToolbarPlugin, null),
45
+ React.createElement("div", { className: "editor-inner" },
46
+ React.createElement(RichTextPlugin, { contentEditable: React.createElement(ContentEditable, { className: "editor-input" }), placeholder: React.createElement(Placeholder, null) }),
47
+ React.createElement(HistoryPlugin, null),
48
+ React.createElement(AutoFocusPlugin, null),
49
+ React.createElement(ListPlugin, null),
50
+ React.createElement(LinkPlugin, null),
51
+ React.createElement(MarkdownShortcutPlugin, { transformers: TRANSFORMERS }))));
52
+ }
@@ -4,6 +4,13 @@
4
4
  "USERNAME": "Benutzername",
5
5
  "PASSWORD": "Passwort",
6
6
  "FORGOT_PASSWORD": "Ich habe mein Passwort vergessen",
7
+ "INSECURE_PASSWORD": "Passwort ist nicht sicher",
8
+ "PASSWORD_NOMATCH": "Passwort ist falsch",
9
+ "PASSWORD_SHORT": "zu kurz",
10
+ "PASSWORD_BAD": "schlecht",
11
+ "PASSWORD_OKAY": "okay",
12
+ "PASSWORD_GOOD": "gut",
13
+ "PASSWORD_PERFECT": "perfekt",
7
14
  "LOGIN": "Einloggen",
8
15
  "LOGIN_HEAD": "Anmeldebereich",
9
16
  "SUBMIT": "Absenden",
@@ -3,6 +3,14 @@
3
3
  "ENTITY.CANCEL": "Cancel",
4
4
  "USERNAME": "Username",
5
5
  "PASSWORD": "Password",
6
+ "PASSWORD_AGAIN": "Password (again)",
7
+ "PASSWORD_SHORT": "too short",
8
+ "PASSWORD_BAD": "bad",
9
+ "PASSWORD_OKAY": "okay",
10
+ "PASSWORD_GOOD": "good",
11
+ "PASSWORD_PERFECT": "perfect",
12
+ "INSECURE_PASSWORD": "Password is not secure",
13
+ "PASSWORD_NOMATCH": "Passwords does not match",
6
14
  "FORGOT_PASSWORD": "I forgot my password",
7
15
  "LOGIN": "Login",
8
16
  "LOGIN_HEAD": "Login Area",
@@ -12,7 +20,7 @@
12
20
  "EMAIL": "Email",
13
21
  "AUTH_CODE": "Authorization Code",
14
22
  "ENTER_AUTH_CODE": "Please enter the verification code that we sent to {email}.",
15
- "CHANGE_EMAIL": "Not your e-mail? <1>Sent to a new email address</1>.",
23
+ "CHANGE_EMAIL": "Not your e-mail? <a>Sent to a new email address</a>.",
16
24
  "PASSWORD_REPEAT": "Password (again)",
17
25
  "VALIDATION.ERROR": "Entity has following errors:",
18
26
  "ENTITY.SAVED": "Changes are saved.",
@@ -4,6 +4,13 @@
4
4
  "USERNAME": "Kullanıcı Adı",
5
5
  "PASSWORD": "Şifre",
6
6
  "FORGOT_PASSWORD": "Şifremi unuttum",
7
+ "INSECURE_PASSWORD": "Şifre güvenli değil",
8
+ "PASSWORD_NOMATCH": "Şifreler uyuşmuyor",
9
+ "PASSWORD_SHORT": "çok kısa",
10
+ "PASSWORD_BAD": "kötü",
11
+ "PASSWORD_OKAY": "idare eder",
12
+ "PASSWORD_GOOD": "iyi",
13
+ "PASSWORD_PERFECT": "harika",
7
14
  "SUBMIT": "Gönder",
8
15
  "LOGIN": "Giriş",
9
16
  "LOGIN_HEAD": "Oturum Açma Alanı",
@@ -18,4 +18,6 @@ import { useMenuState, useIsMobile } from './Components/MenuState';
18
18
  import TopProgressBar from './Components/TopProgressBar';
19
19
  import ThemeProvider, { useTheme, useAllThemes } from './Components/ThemeProvider';
20
20
  import StepList, { StepItem } from './Components/StepList';
21
- export { ThemeProvider, useTheme, useAllThemes, useIsMobile, useMenuState, StepList, StepItem, TopProgressBar, CRUD, ModalEntityEditor, CRUDActions, Relative, ApiSelect, Preview, ExcelExportButton, ExternalLoginButton, SingleFilePicker, MultiFilePicker, ImagePicker, BootstrapTable, EntityEditor, GoToTop, Validator, ValueValidator, ValidationErrors, LoadingButton, BootstrapDataTable, IdColumn, Column, ActionsColumn, Actions, useDataTableContext, LanguageProvider, useLanguage, LanguageSwitcher, ErrorBoundary, CheckBox };
21
+ import PasswordInput from './Components/PasswordInput';
22
+ import DefaultValidatorOptions from './Components/DefaultValidatorOptions';
23
+ export { ThemeProvider, useTheme, useAllThemes, useIsMobile, useMenuState, DefaultValidatorOptions, PasswordInput, StepList, StepItem, TopProgressBar, CRUD, ModalEntityEditor, CRUDActions, Relative, ApiSelect, Preview, ExcelExportButton, ExternalLoginButton, SingleFilePicker, MultiFilePicker, ImagePicker, BootstrapTable, EntityEditor, GoToTop, Validator, ValueValidator, ValidationErrors, LoadingButton, BootstrapDataTable, IdColumn, Column, ActionsColumn, Actions, useDataTableContext, LanguageProvider, useLanguage, LanguageSwitcher, ErrorBoundary, CheckBox };
package/lib/esm/index.js CHANGED
@@ -18,4 +18,6 @@ import { useMenuState, useIsMobile } from './Components/MenuState';
18
18
  import TopProgressBar from './Components/TopProgressBar';
19
19
  import ThemeProvider, { useTheme, useAllThemes } from './Components/ThemeProvider';
20
20
  import StepList, { StepItem } from './Components/StepList';
21
- export { ThemeProvider, useTheme, useAllThemes, useIsMobile, useMenuState, StepList, StepItem, TopProgressBar, CRUD, ModalEntityEditor, CRUDActions, Relative, ApiSelect, Preview, ExcelExportButton, ExternalLoginButton, SingleFilePicker, MultiFilePicker, ImagePicker, BootstrapTable, EntityEditor, GoToTop, Validator, ValueValidator, ValidationErrors, LoadingButton, BootstrapDataTable, IdColumn, Column, ActionsColumn, Actions, useDataTableContext, LanguageProvider, useLanguage, LanguageSwitcher, ErrorBoundary, CheckBox };
21
+ import PasswordInput from './Components/PasswordInput';
22
+ import DefaultValidatorOptions from './Components/DefaultValidatorOptions';
23
+ export { ThemeProvider, useTheme, useAllThemes, useIsMobile, useMenuState, DefaultValidatorOptions, PasswordInput, StepList, StepItem, TopProgressBar, CRUD, ModalEntityEditor, CRUDActions, Relative, ApiSelect, Preview, ExcelExportButton, ExternalLoginButton, SingleFilePicker, MultiFilePicker, ImagePicker, BootstrapTable, EntityEditor, GoToTop, Validator, ValueValidator, ValidationErrors, LoadingButton, BootstrapDataTable, IdColumn, Column, ActionsColumn, Actions, useDataTableContext, LanguageProvider, useLanguage, LanguageSwitcher, ErrorBoundary, CheckBox };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-admin-base-bootstrap",
3
- "version": "0.7.4",
3
+ "version": "0.7.7",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",
@@ -25,29 +25,32 @@
25
25
  "delay": "100"
26
26
  },
27
27
  "peerDependencies": {
28
- "react": "^18.0.0",
28
+ "react": "^18.2.0",
29
+ "react-dom": "^18.2.0",
29
30
  "react-intl": "^5.21.0",
30
31
  "react-router-dom": "^6.3.0"
31
32
  },
32
33
  "dependencies": {
33
- "@emotion/react": "^11.8.2",
34
+ "@emotion/react": "^11.9.3",
34
35
  "@fortawesome/fontawesome-free": "^6.1.1",
35
36
  "bootstrap": "^5.1.3",
36
37
  "file-dialog": "^0.0.8",
37
38
  "modal-cropper": "^1.2.3",
38
39
  "nprogress": "^0.2.0",
39
40
  "prettysize": "^2.0.0",
40
- "react-admin-base": "^0.7.1",
41
+ "react-admin-base": "^0.7.3",
42
+ "react-password-strength-bar": "^0.4.1",
41
43
  "react-responsive": "^8.2.0",
42
- "react-select": "^5.2.2",
43
- "reactstrap": "^9.0.1",
44
+ "react-select": "^5.3.2",
45
+ "reactstrap": "^9.1.1",
44
46
  "rewire": "^6.0.0",
45
- "sweetalert2": "^11.4.8"
47
+ "sweetalert2": "^11.4.17"
46
48
  },
47
49
  "devDependencies": {
50
+ "@types/react": "^18.0.12",
48
51
  "cross-env": "^7.0.3",
49
- "nodemon": "^2.0.15",
50
- "react-intl": "^5.24.8",
51
- "typescript": "^4.6.3"
52
+ "nodemon": "^2.0.16",
53
+ "react-intl": "^6.0.4",
54
+ "typescript": "^4.7.3"
52
55
  }
53
56
  }
@@ -1,5 +1,5 @@
1
1
  import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
2
- import { DataContextProvider, useAuth, useDataTable } from 'react-admin-base';
2
+ import { DataContextProvider, RefreshScope, useAuth, useDataTable } from 'react-admin-base';
3
3
  import { FormattedMessage, useIntl } from "react-intl";
4
4
  import { Link } from 'react-router-dom';
5
5
  import { Alert, Button, Card, CardFooter, CardHeader, Col, Input, Row, Table } from 'reactstrap';
@@ -64,10 +64,11 @@ export function Column(props) {
64
64
  return <th
65
65
  {...props}
66
66
  style={props.sort && {cursor: 'pointer'}}
67
- onClick={props.sort && (() => setParams({
68
- sort: sort,
67
+ onClick={props.sort && (() => setParams(params => ({
68
+ ...params,
69
+ sort: sort,
69
70
  desc: params.sort === sort ? !params.desc : true
70
- }))}
71
+ })))}
71
72
  >{props.children} {sort && params.sort && params.sort === sort ? params.desc ?
72
73
  <i className="fa fa-sort-down"/> :
73
74
  <i className="fa fa-sort-up"/> : ''}
@@ -77,7 +78,7 @@ export function Column(props) {
77
78
  export default function BootstrapTable({url, bordered, noStrip, defaultParams, add, children, innerRef, body}: any) {
78
79
  var state = useState({sort: 'id', ...defaultParams});
79
80
  const [params, setParams] = state;
80
- const [page, lastPage, setPage, data, itemPerPage, setItemPerPage] = useDataTable(url, params, body);
81
+ const [page, lastPage, setPage, data, itemPerPage, setItemPerPage, update] = useDataTable(url, params, body);
81
82
  const intl = useIntl();
82
83
  const [ api ] = useAuth();
83
84
 
@@ -98,7 +99,7 @@ export default function BootstrapTable({url, bordered, noStrip, defaultParams, a
98
99
  };
99
100
  }
100
101
  }, [setParams, innerRef]);
101
-
102
+
102
103
  const fetchData = useCallback(async function(extraParams) {
103
104
  if (body) {
104
105
  const data = await api.tokenized.post(url, body, { params: { ...params, ...(extraParams || {}) } });
@@ -112,47 +113,49 @@ export default function BootstrapTable({url, bordered, noStrip, defaultParams, a
112
113
  return <Card>
113
114
  <DataTableContext.Provider value={state}>
114
115
  <DataContextProvider value={fetchData}>
115
- <CardHeader>
116
- <Row>
117
- {add && <Col xs="12" md="2"><Link to={add} className="btn btn-primary font-xl d-block"><i className="fa fa-plus"/></Link></Col>}
118
- <Col md="2">
119
- <Input type="select" value={itemPerPage.toString()} onChange={a => setItemPerPage(+a.currentTarget.value)}>
120
- <option value="1">1</option>
121
- <option value="20">20</option>
122
- <option value="50">50</option>
123
- <option value="100">100</option>
124
- <option value="150">150</option>
125
- <option value="200">200</option>
126
- <option value="-1">{intl.formatMessage({id: "ALL"})}</option>
127
- </Input>
128
- </Col>
129
- {children[2]}
130
- <Col md="3" className="ms-auto">
131
- <Input
132
- placeholder={intl.formatMessage({id: "SEARCH"})} type="text"
133
- value={params.query || ''}
134
- onChange={e => setParams({...params, query: e.currentTarget.value})}
116
+ <RefreshScope update={update}>
117
+ <CardHeader>
118
+ <Row>
119
+ {add && <Col xs="12" md="2"><Link to={add} className="btn btn-primary font-xl d-block"><i className="fa fa-plus"/></Link></Col>}
120
+ <Col md="2">
121
+ <Input type="select" value={itemPerPage.toString()} onChange={a => setItemPerPage(+a.currentTarget.value)}>
122
+ <option value="1">1</option>
123
+ <option value="20">20</option>
124
+ <option value="50">50</option>
125
+ <option value="100">100</option>
126
+ <option value="150">150</option>
127
+ <option value="200">200</option>
128
+ <option value="-1">{intl.formatMessage({id: "ALL"})}</option>
129
+ </Input>
130
+ </Col>
131
+ {children[2]}
132
+ <Col md="3" className="ms-auto">
133
+ <Input
134
+ placeholder={intl.formatMessage({id: "SEARCH"})} type="text"
135
+ value={params.query || ''}
136
+ onChange={e => setParams({...params, query: e.currentTarget.value})}
137
+ />
138
+ </Col>
139
+ </Row>
140
+ </CardHeader>
141
+ {data === null ? <Alert className="text-center mb-0 mx-3 " color="warning"><i className="fas fa-spinner fa-spin"></i></Alert> : !data.length ? <Alert className="text-center mx-3" color="danger">
142
+ <i className="far fa-times-circle"></i> <FormattedMessage id="NO_DATA_IS_AVAILABLE"/>
143
+ </Alert> : <Table hover bordered={bordered} striped={!noStrip} responsive size="md" className="mb-0 dataTable">
144
+ {children[0]}
145
+ <tbody>
146
+ {data && data.map(children[1].props.children)}
147
+ </tbody>
148
+ </Table>}
149
+ { lastPage > 1 && <CardFooter>
150
+ <nav>
151
+ <BootstrapPagination
152
+ currentPage={page}
153
+ pageCount={lastPage}
154
+ onPageChange={index => setPage(index)}
135
155
  />
136
- </Col>
137
- </Row>
138
- </CardHeader>
139
- {data === null ? <Alert className="text-center mb-0 mx-3 " color="warning"><i className="fas fa-spinner fa-spin"></i></Alert> : !data.length ? <Alert className="text-center mx-3" color="danger">
140
- <i className="far fa-times-circle"></i> <FormattedMessage id="NO_DATA_IS_AVAILABLE"/>
141
- </Alert> : <Table hover bordered={bordered} striped={!noStrip} responsive size="md" className="mb-0 dataTable">
142
- {children[0]}
143
- <tbody>
144
- {data && data.map(children[1].props.children)}
145
- </tbody>
146
- </Table>}
147
- { lastPage > 1 && <CardFooter>
148
- <nav>
149
- <BootstrapPagination
150
- currentPage={page}
151
- pageCount={lastPage}
152
- onPageChange={index => setPage(index)}
153
- />
154
- </nav>
155
- </CardFooter> }
156
+ </nav>
157
+ </CardFooter> }
158
+ </RefreshScope>
156
159
  </DataContextProvider>
157
160
  </DataTableContext.Provider>
158
161
  </Card>;
@@ -0,0 +1,29 @@
1
+ import React, { useMemo } from "react";
2
+ import { ValidatorOptionProvider } from "react-admin-base";
3
+ import { useIntl } from "react-intl";
4
+ import zxcvbn from 'zxcvbn';
5
+
6
+ export default function DefaultValidatorOptions({ children }) {
7
+ const intl = useIntl();
8
+
9
+ const options = useMemo(() => ({
10
+ validators: {
11
+ password: {
12
+ message: intl.formatMessage({ id: 'PASSWORD_NOMATCH' }),
13
+ rule: function (val, params) {
14
+ return val == params[0];
15
+ }
16
+ },
17
+ securepassword: {
18
+ message: intl.formatMessage({ id: 'INSECURE_PASSWORD' }),
19
+ rule: function (val, params) {
20
+ return !val || (zxcvbn(val, []).score >= 2);
21
+ }
22
+ }
23
+ }
24
+ }), [intl]);
25
+
26
+ return <ValidatorOptionProvider value={options}>
27
+ { children }
28
+ </ValidatorOptionProvider>;
29
+ }
@@ -9,7 +9,7 @@ function ErrorHandler({ error }) {
9
9
  </Alert>;
10
10
  }
11
11
 
12
- export default class ErrorBoundary extends React.Component {
12
+ export default class ErrorBoundary extends React.Component<{ children: React.ReactNode }> {
13
13
  state = { hasError: false, error: null };
14
14
 
15
15
  static getDerivedStateFromError(error) {
@@ -0,0 +1,95 @@
1
+ import React, { useEffect, useMemo, useState } from "react";
2
+ import { FormattedMessage, useIntl } from "react-intl";
3
+ import PasswordStrengthBar from "react-password-strength-bar";
4
+ import { FormGroup, Label, Input } from 'reactstrap';
5
+ import { Validator } from "./Validator";
6
+
7
+ interface BootstrapPasswordInput {
8
+ value?: string;
9
+ onChange: (str: string) => any;
10
+ className?: string;
11
+ disabled?: boolean;
12
+ icon?: string;
13
+ required?: boolean;
14
+ placeholder?: string;
15
+ }
16
+
17
+ function BootstrapPasswordInput({ className, value, onChange, disabled, placeholder }: BootstrapPasswordInput) {
18
+ const intl = useIntl();
19
+
20
+ const short = intl.formatMessage({ id: "PASSWORD_SHORT" });
21
+ const bad = intl.formatMessage({ id: "PASSWORD_BAD" });
22
+ const okay = intl.formatMessage({ id: "PASSWORD_OKAY" });
23
+ const good = intl.formatMessage({ id: "PASSWORD_GOOD" });
24
+ const perfect = intl.formatMessage({ id: "PASSWORD_PERFECT" });
25
+
26
+ const scoreWords = useMemo(() => [short, bad, okay, good, perfect], [short, bad, okay, good, perfect]);
27
+
28
+ return <>
29
+ <Input
30
+ className={className}
31
+ type="password"
32
+ value={value || ''}
33
+ onChange={a => onChange(a.currentTarget.value)}
34
+ disabled={disabled}
35
+ placeholder={placeholder}
36
+ autoComplete="new-password"
37
+ />
38
+ <PasswordStrengthBar
39
+ className="password-str-bar"
40
+ password={value || ''}
41
+ scoreWords={scoreWords}
42
+ shortScoreWord={short}
43
+ />
44
+ </>
45
+ }
46
+
47
+ interface PasswordInputParams {
48
+ value?: string;
49
+ onChange: (str: string) => any;
50
+ className?: string;
51
+ disabled?: boolean;
52
+ icon?: string;
53
+ required?: boolean;
54
+ placeholder?: string;
55
+ }
56
+
57
+ export default function PasswordInput({ value, onChange, className, disabled, icon, required, placeholder }: PasswordInputParams) {
58
+ const [ password2, setPassword2 ] = useState('');
59
+
60
+ const iconElem = icon && <i className={icon} />;
61
+
62
+ useEffect(function() {
63
+ if (!value) {
64
+ setPassword2('');
65
+ }
66
+ }, [value, setPassword2]);
67
+
68
+ return <>
69
+ <FormGroup className={className}>
70
+ {iconElem}
71
+ <Label><FormattedMessage id="PASSWORD" /></Label>
72
+ <Validator name="password" type={[required && "required", "securepassword"].filter(a => !!a)}>
73
+ <BootstrapPasswordInput
74
+ value={value}
75
+ onChange={onChange}
76
+ disabled={disabled}
77
+ placeholder={placeholder}
78
+ />
79
+ </Validator>
80
+ </FormGroup>
81
+ { !!(required || value) && <FormGroup className={className}>
82
+ {iconElem}
83
+ <Label><FormattedMessage id="PASSWORD_AGAIN" /></Label>
84
+ <Validator name="password2" type={[(required || value) && "required", { password: value || '' }].filter(a => !!a)}>
85
+ <Input
86
+ type="password"
87
+ value={password2 || ''}
88
+ onChange={a => setPassword2(a.currentTarget.value)}
89
+ disabled={disabled}
90
+ autoComplete="new-password"
91
+ />
92
+ </Validator>
93
+ </FormGroup> }
94
+ </>;
95
+ }
package/src/i18n/de.json CHANGED
@@ -4,6 +4,13 @@
4
4
  "USERNAME": "Benutzername",
5
5
  "PASSWORD": "Passwort",
6
6
  "FORGOT_PASSWORD": "Ich habe mein Passwort vergessen",
7
+ "INSECURE_PASSWORD": "Passwort ist nicht sicher",
8
+ "PASSWORD_NOMATCH": "Passwort ist falsch",
9
+ "PASSWORD_SHORT": "zu kurz",
10
+ "PASSWORD_BAD": "schlecht",
11
+ "PASSWORD_OKAY": "okay",
12
+ "PASSWORD_GOOD": "gut",
13
+ "PASSWORD_PERFECT": "perfekt",
7
14
  "LOGIN": "Einloggen",
8
15
  "LOGIN_HEAD": "Anmeldebereich",
9
16
  "SUBMIT": "Absenden",
package/src/i18n/en.json CHANGED
@@ -3,6 +3,14 @@
3
3
  "ENTITY.CANCEL": "Cancel",
4
4
  "USERNAME": "Username",
5
5
  "PASSWORD": "Password",
6
+ "PASSWORD_AGAIN": "Password (again)",
7
+ "PASSWORD_SHORT": "too short",
8
+ "PASSWORD_BAD": "bad",
9
+ "PASSWORD_OKAY": "okay",
10
+ "PASSWORD_GOOD": "good",
11
+ "PASSWORD_PERFECT": "perfect",
12
+ "INSECURE_PASSWORD": "Password is not secure",
13
+ "PASSWORD_NOMATCH": "Passwords does not match",
6
14
  "FORGOT_PASSWORD": "I forgot my password",
7
15
  "LOGIN": "Login",
8
16
  "LOGIN_HEAD": "Login Area",
@@ -12,7 +20,7 @@
12
20
  "EMAIL": "Email",
13
21
  "AUTH_CODE": "Authorization Code",
14
22
  "ENTER_AUTH_CODE": "Please enter the verification code that we sent to {email}.",
15
- "CHANGE_EMAIL": "Not your e-mail? <1>Sent to a new email address</1>.",
23
+ "CHANGE_EMAIL": "Not your e-mail? <a>Sent to a new email address</a>.",
16
24
  "PASSWORD_REPEAT": "Password (again)",
17
25
  "VALIDATION.ERROR": "Entity has following errors:",
18
26
  "ENTITY.SAVED": "Changes are saved.",
package/src/i18n/tr.json CHANGED
@@ -4,6 +4,13 @@
4
4
  "USERNAME": "Kullanıcı Adı",
5
5
  "PASSWORD": "Şifre",
6
6
  "FORGOT_PASSWORD": "Şifremi unuttum",
7
+ "INSECURE_PASSWORD": "Şifre güvenli değil",
8
+ "PASSWORD_NOMATCH": "Şifreler uyuşmuyor",
9
+ "PASSWORD_SHORT": "çok kısa",
10
+ "PASSWORD_BAD": "kötü",
11
+ "PASSWORD_OKAY": "idare eder",
12
+ "PASSWORD_GOOD": "iyi",
13
+ "PASSWORD_PERFECT": "harika",
7
14
  "SUBMIT": "Gönder",
8
15
  "LOGIN": "Giriş",
9
16
  "LOGIN_HEAD": "Oturum Açma Alanı",
package/src/index.ts CHANGED
@@ -19,10 +19,14 @@ import { useMenuState, useIsMobile } from './Components/MenuState';
19
19
  import TopProgressBar from './Components/TopProgressBar';
20
20
  import ThemeProvider, { useTheme, useAllThemes } from './Components/ThemeProvider';
21
21
  import StepList, { StepItem } from './Components/StepList';
22
+ import PasswordInput from './Components/PasswordInput';
23
+ import DefaultValidatorOptions from './Components/DefaultValidatorOptions';
22
24
 
23
25
  export {
24
26
  ThemeProvider, useTheme, useAllThemes,
25
27
  useIsMobile, useMenuState,
28
+ DefaultValidatorOptions,
29
+ PasswordInput,
26
30
  StepList, StepItem,
27
31
  TopProgressBar,
28
32
  CRUD, ModalEntityEditor, CRUDActions,