@applica-software-guru/react-admin 1.5.326 → 1.5.329

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/package.json CHANGED
@@ -14,6 +14,7 @@
14
14
  "@mui/icons-material": "^5.0.1",
15
15
  "@mui/lab": "5.0.0-alpha.148",
16
16
  "@mui/material": "5.15.17",
17
+ "@react-oauth/google": "^0.12.1",
17
18
  "dayjs": "^1.11.8",
18
19
  "framer-motion": "^10.12.17",
19
20
  "history": "^5.1.0",
@@ -107,5 +108,5 @@
107
108
  "type": "module",
108
109
  "types": "dist/index.d.ts",
109
110
  "typings": "dist/index.d.ts",
110
- "version": "1.5.326"
111
+ "version": "1.5.329"
111
112
  }
@@ -15,6 +15,7 @@ import {
15
15
  } from '@/components';
16
16
  import { AppStateProvider } from '@/components/AppStateProvider';
17
17
  import { ThemeConfig, ThemeProvider } from '@/components/Layout/ThemeProvider';
18
+ import { GoogleOAuthProvider } from '@react-oauth/google';
18
19
  import React, { useMemo } from 'react';
19
20
  import { AdminProps, AuthProvider, DataProvider, I18nProvider } from 'react-admin';
20
21
  import { QueryClient } from 'react-query';
@@ -53,6 +54,7 @@ function ApplicaAdmin({
53
54
  enablePasswordRecover = false,
54
55
  enableThemeToggler = false,
55
56
  enableLocaleSwitcher = false,
57
+ oauth = {},
56
58
  queryClient = defaultQueryClient,
57
59
  background,
58
60
  notificationAPI = 'entities/notification',
@@ -93,11 +95,12 @@ function ApplicaAdmin({
93
95
  version,
94
96
  background,
95
97
  enableRegistration,
96
- enablePasswordRecover
98
+ enablePasswordRecover,
99
+ oauth
97
100
  });
98
101
  }
99
102
  return loginPage;
100
- }, [loginPage, name, version, copy, background, logoMain, enableRegistration, enablePasswordRecover]);
103
+ }, [loginPage, name, version, copy, background, logoMain, enableRegistration, enablePasswordRecover, oauth]);
101
104
  const layout = useMemo(
102
105
  () => (props: any) => {
103
106
  const _logoMain = name ? <MainIcon title={name} /> : logoMain;
@@ -152,7 +155,7 @@ function ApplicaAdmin({
152
155
  );
153
156
  }
154
157
 
155
- return (
158
+ const appContent = (
156
159
  <AppStateProvider>
157
160
  <MenuConfigProvider menu={menu}>
158
161
  <ThemeProvider initialConfig={themeConfig}>
@@ -171,6 +174,12 @@ function ApplicaAdmin({
171
174
  </MenuConfigProvider>
172
175
  </AppStateProvider>
173
176
  );
177
+
178
+ if (oauth.google?.clientId) {
179
+ return <GoogleOAuthProvider clientId={oauth.google.clientId}>{appContent}</GoogleOAuthProvider>;
180
+ } else {
181
+ return appContent;
182
+ }
174
183
  }
175
184
 
176
185
  type ApplicaAdminProps = AdminProps & {
@@ -309,6 +318,14 @@ type ApplicaAdminProps = AdminProps & {
309
318
  * Indicates whether the locale switcher should be displayed.
310
319
  */
311
320
  enableLocaleSwitcher: boolean;
321
+ /**
322
+ * Indicates which OAuth providers to enable and configurations.
323
+ */
324
+ oauth: {
325
+ google?: {
326
+ clientId: string;
327
+ };
328
+ };
312
329
  };
313
330
 
314
331
  export { ApplicaAdmin };
@@ -1,4 +1,4 @@
1
- import { FC, ReactElement, memo, useCallback, useMemo, useState } from 'react';
1
+ import { FC, ReactElement, memo, useCallback, useEffect, useMemo, useState } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { TablePagination, TablePaginationBaseProps, Theme, Toolbar, useMediaQuery } from '@mui/material';
4
4
  import {
@@ -20,6 +20,17 @@ const Pagination: FC<PaginationProps> = memo((props) => {
20
20
  const isSmall = useMediaQuery((theme: Theme) => theme.breakpoints.down('md'));
21
21
  const [currentPage, setCurrentPage] = useState(page - 1); // Stato per la UI
22
22
  const { hasCreate } = useResourceDefinition(props);
23
+ const [isSelectedPage, setIsSelectedPage] = useState(false);
24
+
25
+ useEffect(() => {
26
+ if (page !== currentPage + 1 && !isSelectedPage) {
27
+ setCurrentPage(page - 1);
28
+ }
29
+ }, [currentPage, isSelectedPage, page]);
30
+
31
+ useEffect(() => {
32
+ setIsSelectedPage(false);
33
+ }, [page]);
23
34
 
24
35
  const totalPages = useMemo(() => {
25
36
  return total != null ? Math.ceil(total / perPage) : undefined;
@@ -62,6 +73,7 @@ const Pagination: FC<PaginationProps> = memo((props) => {
62
73
  arrowSelected.includes('KeyboardArrowRightIcon');
63
74
 
64
75
  setCurrentPage(page);
76
+ setIsSelectedPage(true);
65
77
 
66
78
  if (isArrowClick) {
67
79
  // apply debounced API call for arrows clicks
@@ -71,7 +83,7 @@ const Pagination: FC<PaginationProps> = memo((props) => {
71
83
  setPage(page + 1);
72
84
  }
73
85
  },
74
- [debouncedPageChange, setPage, translate, totalPages]
86
+ [debouncedPageChange, setPage, translate, totalPages, setIsSelectedPage]
75
87
  );
76
88
 
77
89
  const handlePerPageChange = useCallback(
@@ -0,0 +1,76 @@
1
+ import { ApplicaAuthProvider, AuthThirdPartyProviders } from '@applica-software-guru/iam-client';
2
+ import { Box, useTheme } from '@mui/material';
3
+ import { GoogleLogin } from '@react-oauth/google';
4
+ import { useAuthProvider, useNotify } from 'ra-core';
5
+ import { useNavigate } from 'react-router-dom';
6
+ import { useEffect, useRef, useState } from 'react';
7
+ import { debounce } from 'lodash';
8
+
9
+ type GoogleLoginButtonProps = {
10
+ setLoading: (loading: boolean) => void;
11
+ };
12
+
13
+ function GoogleLoginButton({ setLoading }: GoogleLoginButtonProps) {
14
+ const theme = useTheme() as any;
15
+ const isDark = theme.palette.mode === 'dark';
16
+ const authProvider = useAuthProvider<ApplicaAuthProvider>();
17
+ const notify = useNotify();
18
+ const navigate = useNavigate();
19
+ const [boxWidth, setBoxWidth] = useState<string | number>(395);
20
+ const boxRef = useRef<HTMLDivElement>(null);
21
+
22
+ useEffect(() => {
23
+ const debouncedUpdateButtonWidth = debounce(() => {
24
+ if (boxRef?.current) {
25
+ setBoxWidth(boxRef.current.offsetWidth);
26
+ }
27
+ }, 100);
28
+
29
+ debouncedUpdateButtonWidth();
30
+ window.addEventListener('resize', debouncedUpdateButtonWidth);
31
+
32
+ return () => {
33
+ window.removeEventListener('resize', debouncedUpdateButtonWidth);
34
+ debouncedUpdateButtonWidth.cancel();
35
+ };
36
+ }, [boxRef]);
37
+
38
+ function handleSuccess(response: any) {
39
+ setLoading(true);
40
+ authProvider
41
+ .thirdPartyLogin({ provider: AuthThirdPartyProviders.GOOGLE, payload: { credential: response.credential } })
42
+ .then(() => {
43
+ setLoading(false);
44
+ navigate('/');
45
+ })
46
+ .catch((error: any) => {
47
+ setLoading(false);
48
+ notify(
49
+ typeof error === 'string'
50
+ ? error
51
+ : typeof error === 'undefined' || !error.message
52
+ ? 'ra.auth.sign_in_error'
53
+ : error.message,
54
+ {
55
+ type: 'error',
56
+ messageArgs: {
57
+ _: typeof error === 'string' ? error : error && error.message ? error.message : undefined
58
+ }
59
+ }
60
+ );
61
+ });
62
+ }
63
+
64
+ function handleError() {
65
+ notify('ra.auth.sign_in_error', {
66
+ type: 'error'
67
+ });
68
+ }
69
+ return (
70
+ <Box sx={{ colorScheme: isDark ? 'light' : 'dark', width: '100%' }} ref={boxRef}>
71
+ <GoogleLogin theme="outline" width={boxWidth} onSuccess={handleSuccess} onError={handleError} />
72
+ </Box>
73
+ );
74
+ }
75
+
76
+ export { GoogleLoginButton };
@@ -4,4 +4,5 @@ export * from './CreateButton';
4
4
  export * from './CreateInDialogButton';
5
5
  export * from './DeleteWithConfirmButton';
6
6
  export * from './EditInDialogButton';
7
+ export * from './GoogleLoginButton';
7
8
  export * from './ImpersonateUserButton';
@@ -2,10 +2,12 @@ import { BaseAuthProps } from './types';
2
2
  import { AuthBackground } from '@/components/AuthBackground';
3
3
  import { AuthWrapper } from '@/components/AuthWrapper';
4
4
  import { TextInput } from '@/components/ra-inputs';
5
- import { Button, CircularProgress, Grid, Link, Stack, Typography } from '@mui/material';
5
+ import { Button, CircularProgress, Divider, Grid, Link, Stack, Typography } from '@mui/material';
6
6
  import { Form, required, useCheckAuth, useLogin, useNotify, useSafeSetState, useTranslate } from 'ra-core';
7
7
  import { useEffect } from 'react';
8
8
  import { Link as RouterLink, useNavigate } from 'react-router-dom';
9
+ import _ from 'lodash';
10
+ import { GoogleLoginButton } from '@/components/ra-buttons';
9
11
 
10
12
  type LoginPageProps = BaseAuthProps & {
11
13
  /**
@@ -39,6 +41,14 @@ type LoginPageProps = BaseAuthProps & {
39
41
  * If not specified, the react-admin home page is used.
40
42
  */
41
43
  redirectTo: string;
44
+ /**
45
+ * Indicates which OAuth providers to enable and configurations.
46
+ */
47
+ oauth?: {
48
+ google?: {
49
+ clientId: string;
50
+ };
51
+ };
42
52
  };
43
53
  function LoginPage({
44
54
  version,
@@ -47,6 +57,7 @@ function LoginPage({
47
57
  logo,
48
58
  enablePasswordRecover = false,
49
59
  enableRegistration = false,
60
+ oauth = {},
50
61
  redirectTo,
51
62
  background = <AuthBackground />
52
63
  }: LoginPageProps): JSX.Element {
@@ -56,6 +67,7 @@ function LoginPage({
56
67
  const notify = useNotify();
57
68
  const checkAuth = useCheckAuth();
58
69
  const navigate = useNavigate();
70
+
59
71
  useEffect(() => {
60
72
  checkAuth({}, false)
61
73
  .then(() => {
@@ -65,6 +77,7 @@ function LoginPage({
65
77
  // not authenticated, stay on the login page
66
78
  });
67
79
  }, [checkAuth, navigate]);
80
+
68
81
  function handleSubmit(values: any) {
69
82
  setLoading(true);
70
83
  login(values, redirectTo)
@@ -88,6 +101,7 @@ function LoginPage({
88
101
  );
89
102
  });
90
103
  }
104
+
91
105
  return (
92
106
  <AuthWrapper name={name} version={version} copy={copy} logo={logo} background={background}>
93
107
  <Grid container spacing={3}>
@@ -153,6 +167,18 @@ function LoginPage({
153
167
  </Button>
154
168
  </Grid>
155
169
  </Form>
170
+ {!_.isEmpty(oauth) ? (
171
+ <>
172
+ <Grid item xs={12} sx={{ mt: 2 }}>
173
+ <Divider />
174
+ </Grid>
175
+ {oauth.google ? (
176
+ <Grid item xs={12} sx={{ mt: 2, width: '100%' }}>
177
+ <GoogleLoginButton setLoading={setLoading} />
178
+ </Grid>
179
+ ) : null}
180
+ </>
181
+ ) : null}
156
182
  </Grid>
157
183
  </Grid>
158
184
  </AuthWrapper>
@@ -26,7 +26,7 @@ function RegisterPage({ name, copy, version, logo, background }: BaseAuthProps)
26
26
  redirect('/login');
27
27
  })
28
28
  .catch((error: any) => {
29
- notify(error, { type: 'error' });
29
+ notify(error.message || error, { type: 'error' });
30
30
  })
31
31
  .finally(() => setLoading(false));
32
32
  }
@@ -41,6 +41,9 @@ function App() {
41
41
  enableRegistration
42
42
  enablePasswordRecover
43
43
  enableThemeToggler
44
+ oauth={{
45
+ google: { clientId: 'YOUR_CLIENT_ID' }
46
+ }}
44
47
  >
45
48
  <CustomRoutes>
46
49
  <Route path="/custom-page" element={<CustomPage />} />