@jasperoosthoek/react-toolbox 0.7.5 → 0.8.0

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.
@@ -2,6 +2,5 @@ export declare const usePrevious: <T>(value: T) => T | undefined;
2
2
  export declare const useDebouncedEffect: (effect: () => void, deps: any[], delay: number) => void;
3
3
  export declare const useForceUpdate: () => () => void;
4
4
  export declare const useSetState: <T>(initialState: T) => [T, (subState: Partial<T>) => void];
5
- export declare const useWithDispatch: <G extends any[]>(obj: (...args: G) => any) => (...args: G) => any;
6
5
  export declare const useInterval: (func: () => void, value: number) => void;
7
6
  export declare const useLocalStorage: <T>(key: string, initialValue: T) => [T, (value: T) => void];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jasperoosthoek/react-toolbox",
3
- "version": "0.7.5",
3
+ "version": "0.8.0",
4
4
  "author": "jasperoosthoek",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -59,10 +59,7 @@
59
59
  "react-dnd": "^16.0.1",
60
60
  "react-dom": "^19.1.0",
61
61
  "react-icons": "^5.4.0",
62
- "react-localization": "^2.0.5",
63
- "react-redux": "^9.2.0",
64
- "redux": "^5.0.1",
65
- "redux-thunk": "^3.1.0"
62
+ "react-localization": "^2.0.5"
66
63
  },
67
64
  "keywords": [
68
65
  "react",
@@ -0,0 +1,88 @@
1
+ import React, { ChangeEvent, KeyboardEvent } from 'react';
2
+ import { Container, Button, Row, Col, Form } from 'react-bootstrap';
3
+ import { useSetState } from '../../utils/hooks';
4
+ import { useLocalization } from '../../localization/LocalizationContext';
5
+
6
+ export type LoginPageProps = {
7
+ onSubmit: (credentials: { email: string; password: string }, callback?: () => void) => void;
8
+ callback?: () => void;
9
+ isAuthenticated: boolean;
10
+ label?: string | React.ReactElement;
11
+ onResetPassword: string | (() => void);
12
+ };
13
+
14
+ export const LoginPage = ({
15
+ onSubmit,
16
+ isAuthenticated,
17
+ label,
18
+ onResetPassword,
19
+ callback,
20
+ }: LoginPageProps) => {
21
+ const [state, setState] = useSetState({ email: '', password: '' });
22
+ const { strings } = useLocalization();
23
+ const submitDisabled = !state.email || !state.password;
24
+
25
+ const handleSubmit = () => {
26
+ if (!submitDisabled) onSubmit(state, callback);
27
+ };
28
+
29
+ if (isAuthenticated) return null;
30
+
31
+ return (
32
+ <Container>
33
+ <Row className="login">
34
+ <Col md={2} />
35
+ <Col md={8}>
36
+ {label || <h1>{strings.getString('login')}</h1>}
37
+ <Form>
38
+ <Form.Group className="mb-3">
39
+ <Form.Label>{strings.getString('your_email')}</Form.Label>
40
+ <Form.Control
41
+ name="email"
42
+ value={state.email}
43
+ onChange={(e: ChangeEvent<HTMLInputElement>) =>
44
+ setState({ email: e.target.value })
45
+ }
46
+ placeholder={strings.getString('enter_email')}
47
+ onKeyDown={(e: KeyboardEvent<HTMLInputElement>) =>
48
+ e.key === 'Enter' && handleSubmit()
49
+ }
50
+ />
51
+ </Form.Group>
52
+ <Form.Group className="mb-3">
53
+ <Form.Label>{strings.getString('your_password')}</Form.Label>
54
+ <Form.Control
55
+ type="password"
56
+ name="password"
57
+ value={state.password}
58
+ onChange={(e: ChangeEvent<HTMLInputElement>) =>
59
+ setState({ password: e.target.value })
60
+ }
61
+ placeholder={strings.getString('enter_password')}
62
+ onKeyDown={(e: KeyboardEvent<HTMLInputElement>) =>
63
+ e.key === 'Enter' && handleSubmit()
64
+ }
65
+ />
66
+ </Form.Group>
67
+ </Form>
68
+ <Button onClick={handleSubmit} disabled={submitDisabled}>
69
+ {strings.getString('login')}
70
+ </Button>
71
+ <p className="mt-2">
72
+ {strings.getString('forgot_password')}{' '}
73
+ {typeof onResetPassword === 'string'
74
+ ? <a href={onResetPassword}>{strings.getString('reset_password')}</a>
75
+ : <span
76
+ onClick={onResetPassword}
77
+ className='reset-password'
78
+ >
79
+ {strings.getString('reset_password')}
80
+ </span>
81
+ }
82
+ </p>
83
+ </Col>
84
+ <Col md={2} />
85
+ </Row>
86
+ </Container>
87
+ );
88
+ };
package/src/index.ts CHANGED
@@ -22,7 +22,7 @@ export * from './utils/hooks';
22
22
  export * from './utils/timeAndDate';
23
23
  export * from './utils/utils';
24
24
 
25
- export * from './login/Login';
25
+ export * from './components/login/LoginPage';
26
26
 
27
27
  export * from './localization/localization';
28
28
  export * from './localization/LocalizationContext';
@@ -1,5 +1,4 @@
1
1
  import React, { useRef, useEffect, useState, useCallback } from 'react';
2
- import { useDispatch } from 'react-redux';
3
2
 
4
3
  // https://stackoverflow.com/questions/53446020/how-to-compare-oldvalues-and-newvalues-on-react-hooks-useeffect
5
4
  export const usePrevious = <T>(value: T): T | undefined => {
@@ -41,12 +40,6 @@ export const useSetState = <T>(initialState: T): [T, (subState: Partial<T>) => v
41
40
  return [state, setSubState];
42
41
  }
43
42
 
44
- export const useWithDispatch = <G extends any[]>(obj: (...args: G) => any) => {
45
- const dispatch = useDispatch();
46
-
47
- return (...args: G) => dispatch(obj(...args));
48
- };
49
-
50
43
  // https://devtrium.com/posts/set-interval-react
51
44
  export const useInterval = (func: () => void, value: number) => useEffect(() => {
52
45
  if (typeof func !== 'function') {
@@ -1,333 +0,0 @@
1
- import React, { ReactElement, ChangeEvent, KeyboardEvent } from 'react';
2
- import { useSelector, useStore, useDispatch } from 'react-redux';
3
- import { ThunkAction, ThunkDispatch } from 'redux-thunk';
4
-
5
- import axios, { AxiosResponse, AxiosInstance } from 'axios';
6
- import { Container, Button, Row, Col, Form } from 'react-bootstrap';
7
-
8
- import { useSetState, useWithDispatch } from '../utils/hooks';
9
- import { useLocalization } from '../localization/LocalizationContext';
10
- import { isEmpty } from '../utils/utils';
11
-
12
- export const LOGIN_SET_TOKEN = 'LOGIN_SET_TOKEN';
13
- export const LOGIN_SET_CURRENT_USER = 'LOGIN_SET_CURRENT_USER';
14
- export const LOGIN_UNSET_CURRENT_USER = 'LOGIN_UNSET_CURRENT_USER';
15
-
16
- export type LoginActions =
17
- | { type: 'LOGIN_SET_TOKEN'; payload: string }
18
- | { type: 'LOGIN_SET_CURRENT_USER'; payload: any }
19
- | { type: 'LOGIN_UNSET_CURRENT_USER' };
20
-
21
- const useThunkDispatch = () => {
22
- const dispatch = useDispatch() as ThunkDispatch<any, undefined, LoginActions>;
23
- return dispatch;
24
- };
25
-
26
- export type AuthState = {
27
- isAuthenticated: boolean;
28
- user: any | null;
29
- token: string;
30
- }
31
-
32
- type Action = {
33
- type: string,
34
- [key: string]: any,
35
- }
36
- export type LoginFactoryProps = {
37
- authenticatedComponent: (props: any) => ReactElement;
38
- passwordResetUrl: string;
39
- axios: typeof axios | AxiosInstance;
40
- onError: (error: any) => void;
41
- onLogout?: () => void;
42
- loginUrl: string;
43
- getUserUrl: string;
44
- logoutUrl: string;
45
- localStoragePrefix: string;
46
- };
47
-
48
- export type LoginProps = {
49
- label?: string | ReactElement;
50
- };
51
- const errorIfUndefined = (obj: Partial<LoginFactoryProps>) => Object.entries(obj).reduce((error, [param, value]) => {
52
- if (typeof value === 'undefined') {
53
- console.error(`Parameter ${param} of loginFactory cannot be undefined`);
54
- }
55
- return error;
56
- }, false);
57
-
58
- export const loginFactory = ({
59
- authenticatedComponent,
60
- passwordResetUrl,
61
- axios,
62
- onError = () => {},
63
- onLogout,
64
- loginUrl,
65
- getUserUrl,
66
- logoutUrl,
67
- localStoragePrefix,
68
- }: LoginFactoryProps) => {
69
-
70
- const localStorageUser = localStoragePrefix ? `${localStoragePrefix}-user` : 'user';
71
- const localStorageToken = localStoragePrefix ? `${localStoragePrefix}-token` : 'token';
72
- const localStorageState = localStoragePrefix ? `${localStoragePrefix}-state` : 'state';
73
-
74
- errorIfUndefined({
75
- authenticatedComponent,
76
- passwordResetUrl,
77
- axios,
78
- loginUrl,
79
- getUserUrl,
80
- logoutUrl,
81
- });
82
-
83
- const AuthenticatedComponent = authenticatedComponent;
84
-
85
- const login = (
86
- userData: any,
87
- callback?: () => void
88
- ): ThunkAction<Promise<void>, any, undefined, LoginActions> => {
89
- return async (dispatch: ThunkDispatch<any, undefined, LoginActions>) => {
90
- try {
91
- const response = await axios.post(loginUrl, userData);
92
- const { auth_token } = response.data;
93
- dispatch(setToken(auth_token));
94
- dispatch(getCurrentUser());
95
- if (typeof callback === 'function') callback();
96
- } catch (error) {
97
- dispatch(unsetCurrentUser());
98
- onError(error);
99
- }
100
- };
101
- };
102
-
103
- const setAxiosAuthToken = (token: string) => {
104
- if (typeof token !== 'undefined' && token) {
105
- // Apply for every request
106
- axios.defaults.headers.common['Authorization'] = 'Token ' + token;
107
- } else {
108
- // Delete auth header
109
- delete axios.defaults.headers.common['Authorization'];
110
- }
111
- };
112
-
113
- const getCurrentUser = ({ callback }: { callback?: (userData: any) => void} = {}) => async (dispatch: ThunkDispatch<any, undefined, any>) => {
114
- try {
115
- const response = await axios.get(getUserUrl) as AxiosResponse;
116
- dispatch(setCurrentUser(response.data));
117
- if (typeof callback === 'function') callback(response.data);
118
-
119
- } catch(error) {
120
- dispatch(unsetCurrentUser());
121
- onError(error);
122
- };
123
- };
124
-
125
- const setCurrentUser = (user: any) => (dispatch: ThunkDispatch<any, undefined, any>) => {
126
- localStorage.setItem(localStorageUser, JSON.stringify(user));
127
- dispatch({
128
- type: LOGIN_SET_CURRENT_USER,
129
- payload: user,
130
- });
131
- };
132
-
133
- const setToken = (token: string) => (dispatch: ThunkDispatch<any, undefined, any>) => {
134
- setAxiosAuthToken(token);
135
- localStorage.setItem(localStorageToken, token);
136
- dispatch({
137
- type: LOGIN_SET_TOKEN,
138
- payload: token,
139
- });
140
- };
141
-
142
- const unsetCurrentUser = () => (dispatch: ThunkDispatch<any, undefined, any>) => {
143
- setAxiosAuthToken('');
144
- localStorage.removeItem(localStorageToken);
145
- localStorage.removeItem(localStorageUser);
146
- localStorage.removeItem(localStorageState);
147
- dispatch({
148
- type: LOGIN_UNSET_CURRENT_USER
149
- });
150
- };
151
-
152
- const logout = () => async (dispatch: ThunkDispatch<any, undefined, any>) => {
153
- try {
154
- await axios.post(logoutUrl);
155
- dispatch(unsetCurrentUser());
156
- if (typeof onLogout === 'function') onLogout();
157
- } catch(error) {
158
- dispatch(unsetCurrentUser());
159
- onError(error);
160
- };
161
- };
162
-
163
- const useLogin = () => {
164
- return (
165
- {
166
- login: useWithDispatch(login),
167
- getCurrentUser: useWithDispatch(getCurrentUser),
168
- setCurrentUser: useWithDispatch(setCurrentUser),
169
- setToken: useWithDispatch(setToken),
170
- unsetCurrentUser: useWithDispatch(unsetCurrentUser),
171
- logout: useWithDispatch(logout),
172
- }
173
- );
174
- }
175
-
176
- const token = localStorage.getItem(localStorageToken);
177
- const user = localStorage.getItem(localStorageUser);
178
-
179
- const hasLocalStorage = !isEmpty(user) && !isEmpty(token);
180
- const initialState = {
181
- ...hasLocalStorage
182
- ? {
183
- isAuthenticated: true,
184
- user: user ? JSON.parse(user) : null,
185
- token,
186
- }
187
- : {
188
- isAuthenticated: false,
189
- user: null,
190
- token: ''
191
- },
192
- } as AuthState;
193
- if (hasLocalStorage && token) {
194
- setAxiosAuthToken(token);
195
- }
196
-
197
- const authReducer = {
198
- auth:
199
- (state = initialState, action: Action) => {
200
- switch (action.type) {
201
- case LOGIN_SET_TOKEN:
202
- return {
203
- ...state,
204
- isAuthenticated: true,
205
- token: action.payload,
206
- };
207
- case LOGIN_SET_CURRENT_USER:
208
- return {
209
- ...state,
210
- user: action.payload,
211
- };
212
- case LOGIN_UNSET_CURRENT_USER:
213
- return initialState;
214
- default:
215
- return state;
216
- }
217
- },
218
- };
219
- const useAuth = () => {
220
- return useSelector((state: any) => state.auth);
221
- }
222
- const Login = ({ label }: LoginProps) => {
223
- const [state, setState] = useSetState({
224
- email: '',
225
- password: ''
226
- });
227
- const isAuthenticated = useSelector(({ auth }: any) => auth.isAuthenticated );
228
- const dispatch = useThunkDispatch();
229
- const { strings } = useLocalization();
230
-
231
- const onChange = (e: ChangeEvent<HTMLInputElement>) => {
232
- setState({ [e.target.name]: e.target.value });
233
- };
234
- const submitDisabled = !state.email || !state.password;
235
- const onLoginClick = () => {
236
- if (submitDisabled) return;
237
- const userData = {
238
- email: state.email,
239
- password: state.password
240
- };
241
- dispatch(login(userData));
242
- };
243
-
244
- if (isAuthenticated) {
245
- return <AuthenticatedComponent />
246
- }
247
- return (
248
- <Container>
249
- <Row className="login">
250
- <Col md='2'>
251
- </Col>
252
- <Col md='8'>
253
- {label || <h1>{strings.getString('login')}</h1>}
254
- <Form>
255
- <Form.Group className="mb-3">
256
- <Form.Label>{strings.getString('your_email')}</Form.Label>
257
- <Form.Control
258
- type='text'
259
- name='email'
260
- placeholder={strings.getString('enter_email')}
261
- value={state.email}
262
- onChange={onChange}
263
- onKeyDown={(e: KeyboardEvent<HTMLInputElement>) => {
264
- if (e.key === 'Enter') {
265
- e.preventDefault();
266
- if (!submitDisabled) onLoginClick();
267
- }
268
- }}
269
- />
270
- </Form.Group>
271
-
272
- <Form.Group className="mb-3">
273
- <Form.Label>{strings.getString('your_password')}</Form.Label>
274
- <Form.Control
275
- type='password'
276
- name='password'
277
- placeholder={strings.getString('enter_password')}
278
- value={state.password}
279
- onChange={onChange}
280
- onKeyDown={(e: KeyboardEvent<HTMLInputElement>) => {
281
- if (e.key === 'Enter') {
282
- e.preventDefault();
283
- if (!submitDisabled) onLoginClick();
284
- }
285
- }}
286
- />
287
- </Form.Group>
288
- </Form>
289
- <Button
290
- variant='primary'
291
- onClick={onLoginClick}
292
- disabled={submitDisabled}
293
- >
294
- {strings.getString('login')}
295
- </Button>
296
- <p className='mt-2'>
297
- {strings.getString('forgot_password')}{' '}
298
- <a href={passwordResetUrl}>{strings.getString('reset_password')}</a>
299
- </p>
300
- </Col>
301
- <Col md='2'>
302
- </Col>
303
- </Row>
304
- </Container>
305
- );
306
- };
307
-
308
- return {
309
- Login,
310
- useLogin,
311
- login,
312
- getCurrentUser,
313
- setCurrentUser,
314
- setToken,
315
- unsetCurrentUser,
316
- logout,
317
- authReducer,
318
- useAuth,
319
- storeState: (state: any, action: Action) => {
320
- if (action.type === LOGIN_UNSET_CURRENT_USER) {
321
- localStorage.removeItem(localStorageState);
322
- } else {
323
- localStorage.setItem(localStorageState, JSON.stringify(state));
324
- }
325
- },
326
- retrieveState: () => {
327
- const state = localStorage.getItem(localStorageState);
328
- if (!state) return {};
329
- return JSON.parse(state);
330
- },
331
- }
332
- }
333
-