@applica-software-guru/react-admin 1.3.163 → 1.3.166
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/dist/ApplicaAdmin.d.ts +43 -36
- package/dist/ApplicaAdmin.d.ts.map +1 -1
- package/dist/components/AuthWrapper.d.ts +20 -17
- package/dist/components/AuthWrapper.d.ts.map +1 -1
- package/dist/components/MainIcon.d.ts +4 -9
- package/dist/components/MainIcon.d.ts.map +1 -1
- package/dist/components/Onboarding/OnboardingTip.d.ts +5 -0
- package/dist/components/Onboarding/OnboardingTip.d.ts.map +1 -0
- package/dist/components/Onboarding/Provider.d.ts +49 -0
- package/dist/components/Onboarding/Provider.d.ts.map +1 -0
- package/dist/components/Onboarding/enums.d.ts +7 -0
- package/dist/components/Onboarding/enums.d.ts.map +1 -0
- package/dist/components/Onboarding/hooks.d.ts +10 -0
- package/dist/components/Onboarding/hooks.d.ts.map +1 -0
- package/dist/components/Onboarding/index.d.ts +7 -0
- package/dist/components/Onboarding/index.d.ts.map +1 -0
- package/dist/components/Onboarding/onboardingDataProvider.d.ts +8 -0
- package/dist/components/Onboarding/onboardingDataProvider.d.ts.map +1 -0
- package/dist/components/Onboarding/schemas.d.ts +5 -0
- package/dist/components/Onboarding/schemas.d.ts.map +1 -0
- package/dist/components/Onboarding/types.d.ts +8 -0
- package/dist/components/Onboarding/types.d.ts.map +1 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/ra-pages/ActivatePage.d.ts +1 -1
- package/dist/components/ra-pages/ActivatePage.d.ts.map +1 -1
- package/dist/components/ra-pages/LoginPage.d.ts +11 -11
- package/dist/components/ra-pages/LoginPage.d.ts.map +1 -1
- package/dist/components/ra-pages/RecoverPage.d.ts +1 -1
- package/dist/components/ra-pages/RecoverPage.d.ts.map +1 -1
- package/dist/components/ra-pages/RegisterPage.d.ts +1 -1
- package/dist/components/ra-pages/RegisterPage.d.ts.map +1 -1
- package/dist/components/ra-pages/types.d.ts +8 -5
- package/dist/components/ra-pages/types.d.ts.map +1 -1
- package/dist/hooks/index.d.ts +14 -14
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/useLocalStorage.d.ts +2 -1
- package/dist/hooks/useLocalStorage.d.ts.map +1 -1
- package/dist/react-admin.cjs.js +70 -67
- package/dist/react-admin.cjs.js.map +1 -1
- package/dist/react-admin.es.js +13841 -12034
- package/dist/react-admin.es.js.map +1 -1
- package/dist/react-admin.umd.js +71 -68
- package/dist/react-admin.umd.js.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/localStorage.d.ts +16 -0
- package/dist/utils/localStorage.d.ts.map +1 -0
- package/package.json +3 -2
- package/src/ApplicaAdmin.tsx +46 -36
- package/src/components/{AuthWrapper.jsx → AuthWrapper.tsx} +13 -4
- package/src/components/{MainIcon.jsx → MainIcon.tsx} +4 -8
- package/src/components/Onboarding/OnboardingTip.tsx +52 -0
- package/src/components/Onboarding/Provider.tsx +145 -0
- package/src/components/Onboarding/enums.ts +7 -0
- package/src/components/Onboarding/hooks.tsx +80 -0
- package/src/components/Onboarding/index.ts +6 -0
- package/src/components/Onboarding/onboardingDataProvider.tsx +128 -0
- package/src/components/Onboarding/schemas.ts +6 -0
- package/src/components/Onboarding/types.ts +8 -0
- package/src/components/index.jsx +1 -0
- package/src/components/ra-pages/ActivatePage.tsx +2 -2
- package/src/components/ra-pages/LoginPage.tsx +12 -12
- package/src/components/ra-pages/RecoverPage.tsx +2 -2
- package/src/components/ra-pages/RegisterPage.tsx +2 -2
- package/src/components/ra-pages/types.ts +8 -5
- package/src/hooks/useLocalStorage.tsx +51 -0
- package/src/playground/components/pages/CustomPage.jsx +23 -8
- package/src/utils/index.ts +1 -0
- package/src/utils/localStorage.ts +73 -0
- package/src/hooks/useLocalStorage.jsx +0 -31
- /package/src/hooks/{index.jsx → index.ts} +0 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import { useLocalStorage } from '../../hooks';
|
|
3
|
+
import { OnboardingModes } from './enums';
|
|
4
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
5
|
+
import * as yup from 'yup';
|
|
6
|
+
import { onboardingModesSchema, onboardingTopicSchema } from './schemas';
|
|
7
|
+
import { useDataProvider, useGetIdentity } from 'ra-core';
|
|
8
|
+
import { useQuery, useQueryClient, useMutation } from 'react-query';
|
|
9
|
+
|
|
10
|
+
type IOnboardingDataProvider = {
|
|
11
|
+
onboardingRequired: boolean;
|
|
12
|
+
acknowledgeOnboarding: () => Promise<void>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function useUserProfileDataProvider(topic: string): (IOnboardingDataProvider | object) & { loading: boolean; error: boolean } {
|
|
16
|
+
const queryClient = useQueryClient(),
|
|
17
|
+
[onboardingRequired, setOnboardingRequired] = useState(false),
|
|
18
|
+
[acknowledgeOnboarding, setAcknowledgeOnboarding] = useState(() => () => Promise.resolve()),
|
|
19
|
+
identity = useGetIdentity(),
|
|
20
|
+
user = identity?.data?.email,
|
|
21
|
+
dataProvider = useDataProvider(),
|
|
22
|
+
fetch = useCallback(() => {
|
|
23
|
+
if (!user) {
|
|
24
|
+
return Promise.reject();
|
|
25
|
+
} else {
|
|
26
|
+
return dataProvider.get('onboarding').then((response: any) => {
|
|
27
|
+
return response?.data?.value;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}, [dataProvider, user]),
|
|
31
|
+
{ data, isLoading, isError } = useQuery({
|
|
32
|
+
queryKey: ['GET', 'onboarding', user],
|
|
33
|
+
queryFn: fetch,
|
|
34
|
+
staleTime: 24 * 60 * 60 * 1000,
|
|
35
|
+
retry: user !== undefined
|
|
36
|
+
}),
|
|
37
|
+
[post, setPost] = useState(() => () => Promise.resolve()),
|
|
38
|
+
mutation = useMutation({ mutationFn: post }),
|
|
39
|
+
mutateAsync = mutation.mutateAsync;
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
function post() {
|
|
43
|
+
const newValue = _.chain(data ?? {})
|
|
44
|
+
.clone()
|
|
45
|
+
.extend({ [topic]: Date.now() })
|
|
46
|
+
.value();
|
|
47
|
+
return dataProvider.post('onboarding', newValue).then(() => {
|
|
48
|
+
queryClient.invalidateQueries(['GET', 'onboarding', user]);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
setPost(() => post);
|
|
52
|
+
}, [dataProvider, data, topic, queryClient, user]);
|
|
53
|
+
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
setOnboardingRequired(_.isNil((data ?? {})[topic]));
|
|
56
|
+
}, [data, topic, setOnboardingRequired]);
|
|
57
|
+
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
setAcknowledgeOnboarding(() => () => {
|
|
60
|
+
return mutateAsync();
|
|
61
|
+
});
|
|
62
|
+
}, [mutateAsync]);
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
loading: isLoading,
|
|
66
|
+
error: isError,
|
|
67
|
+
onboardingRequired: onboardingRequired,
|
|
68
|
+
acknowledgeOnboarding: acknowledgeOnboarding
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function useLocalStorageDataProvider(topic: string): IOnboardingDataProvider {
|
|
73
|
+
const [localStorageValue, setLocalStorageValue] = useLocalStorage('onboarding', {}) as [
|
|
74
|
+
{ [key: string]: number },
|
|
75
|
+
(value: (oldValue: { [key: string]: number }) => { [key: string]: number }) => { [key: string]: number }
|
|
76
|
+
],
|
|
77
|
+
[onboardingRequired, setOnboardingRequired] = useState(false),
|
|
78
|
+
[acknowledgeOnboarding, setAcknowledgeOnboarding] = useState(() => () => Promise.resolve());
|
|
79
|
+
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
setOnboardingRequired(_.isNil((localStorageValue ?? {})[topic]));
|
|
82
|
+
}, [localStorageValue, topic, setOnboardingRequired]);
|
|
83
|
+
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
setAcknowledgeOnboarding(() => () => {
|
|
86
|
+
setLocalStorageValue((oldValue) => _.extend(oldValue, { [topic]: Date.now() }));
|
|
87
|
+
return Promise.resolve();
|
|
88
|
+
});
|
|
89
|
+
}, [topic, setLocalStorageValue, setAcknowledgeOnboarding]);
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
onboardingRequired: onboardingRequired,
|
|
93
|
+
acknowledgeOnboarding: acknowledgeOnboarding
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function useFakeLocalStorageDataProvider(): IOnboardingDataProvider {
|
|
98
|
+
const setterRef = useRef(() => Promise.resolve());
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
onboardingRequired: false,
|
|
102
|
+
acknowledgeOnboarding: setterRef.current
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function useOnboardingDataProvider(mode: OnboardingModes, topic: string): IOnboardingDataProvider {
|
|
107
|
+
yup.object({ mode: onboardingModesSchema, topic: onboardingTopicSchema }).validateSync({ mode: mode, topic: topic });
|
|
108
|
+
const userProfileDataProvider = useUserProfileDataProvider(topic),
|
|
109
|
+
localStorageDataProvider = useLocalStorageDataProvider(topic),
|
|
110
|
+
fakeLocalStorageDataProvider = useFakeLocalStorageDataProvider();
|
|
111
|
+
|
|
112
|
+
switch (mode) {
|
|
113
|
+
case OnboardingModes.USER:
|
|
114
|
+
if (userProfileDataProvider.loading) {
|
|
115
|
+
return fakeLocalStorageDataProvider;
|
|
116
|
+
} else if (userProfileDataProvider.error) {
|
|
117
|
+
return localStorageDataProvider;
|
|
118
|
+
} else {
|
|
119
|
+
return _.pick(userProfileDataProvider, ['onboardingRequired', 'acknowledgeOnboarding']) as IOnboardingDataProvider;
|
|
120
|
+
}
|
|
121
|
+
case OnboardingModes.BROWSER:
|
|
122
|
+
return localStorageDataProvider;
|
|
123
|
+
default:
|
|
124
|
+
return fakeLocalStorageDataProvider;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export { useOnboardingDataProvider };
|
package/src/components/index.jsx
CHANGED
|
@@ -12,6 +12,7 @@ import ScrollX from './ScrollX';
|
|
|
12
12
|
import SmallIcon from './SmallIcon';
|
|
13
13
|
export { MainIcon, ActionsMenu, Loadable, Loader, Layout, Logo, MainCard, ScrollTop, ScrollX, MenuPopover, Notification, SmallIcon };
|
|
14
14
|
export * from './Layout';
|
|
15
|
+
export { OnboardingModes, OnboardingProvider, OnboardingTip, useRestartOnboarding } from './Onboarding';
|
|
15
16
|
|
|
16
17
|
export * from './@extended';
|
|
17
18
|
export * from './third-party';
|
|
@@ -9,7 +9,7 @@ import PropTypes from 'prop-types';
|
|
|
9
9
|
import _ from 'lodash';
|
|
10
10
|
import { useEffect } from 'react';
|
|
11
11
|
|
|
12
|
-
const ActivatePage = ({ name, copy, version, background }: BaseAuthProps) => {
|
|
12
|
+
const ActivatePage = ({ name, copy, logo, version, background }: BaseAuthProps) => {
|
|
13
13
|
const translate = useTranslate();
|
|
14
14
|
const authProvider = useAuthProvider();
|
|
15
15
|
const notify = useNotify();
|
|
@@ -31,7 +31,7 @@ const ActivatePage = ({ name, copy, version, background }: BaseAuthProps) => {
|
|
|
31
31
|
});
|
|
32
32
|
}, [token]);
|
|
33
33
|
return (
|
|
34
|
-
<AuthWrapper name={name} copy={copy} version={version} background={background}>
|
|
34
|
+
<AuthWrapper name={name} copy={copy} logo={logo} version={version} background={background}>
|
|
35
35
|
<Grid container spacing={3}>
|
|
36
36
|
<Grid item xs={12}>
|
|
37
37
|
<Stack direction="row" justifyContent="space-between" alignItems="baseline" sx={{ mb: { xs: -0.5, sm: 0.5 } }}>
|
|
@@ -11,11 +11,11 @@ import { useEffect } from 'react';
|
|
|
11
11
|
|
|
12
12
|
export type LoginPageProps = BaseAuthProps & {
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* Indicates whether to enable the password recovery screen.
|
|
15
|
+
* If enabled, it is advisable to register a route to /recover with a component
|
|
16
|
+
* that takes care of recovery, you can use the Applica component directly for this
|
|
17
17
|
*
|
|
18
|
-
* @see RecoverPage
|
|
18
|
+
* @see RecoverPage for more information.
|
|
19
19
|
* @example
|
|
20
20
|
* import { RecoverPage } from "@applica-software-guru/react-admin";
|
|
21
21
|
* <CustomRoutes noLayout>
|
|
@@ -24,11 +24,11 @@ export type LoginPageProps = BaseAuthProps & {
|
|
|
24
24
|
*/
|
|
25
25
|
enablePasswordRecover?: boolean;
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* Indicates whether to enable the registration screen.
|
|
28
|
+
* If enabled, it is advisable to register a route to /register with a component
|
|
29
|
+
* that takes care of registration, you can use the Applica component directly for this
|
|
30
30
|
*
|
|
31
|
-
* @see RegisterPage
|
|
31
|
+
* @see RegisterPage for more information.
|
|
32
32
|
* @example
|
|
33
33
|
* import { RegisterPage } from "@applica-software-guru/react-admin";
|
|
34
34
|
* <CustomRoutes noLayout>
|
|
@@ -37,12 +37,12 @@ export type LoginPageProps = BaseAuthProps & {
|
|
|
37
37
|
*/
|
|
38
38
|
enableRegistration?: boolean;
|
|
39
39
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
40
|
+
* Indicates the page to redirect the user to after login.
|
|
41
|
+
* If not specified, the react-admin home page is used.
|
|
42
42
|
*/
|
|
43
43
|
redirectTo: string;
|
|
44
44
|
};
|
|
45
|
-
const LoginPage = ({ version, name, copy, enablePasswordRecover, enableRegistration, redirectTo, background }: LoginPageProps) => {
|
|
45
|
+
const LoginPage = ({ version, name, copy, logo, enablePasswordRecover, enableRegistration, redirectTo, background }: LoginPageProps) => {
|
|
46
46
|
const [loading, setLoading] = useSafeSetState(false);
|
|
47
47
|
const login = useLogin();
|
|
48
48
|
const translate = useTranslate();
|
|
@@ -78,7 +78,7 @@ const LoginPage = ({ version, name, copy, enablePasswordRecover, enableRegistrat
|
|
|
78
78
|
});
|
|
79
79
|
};
|
|
80
80
|
return (
|
|
81
|
-
<AuthWrapper name={name} version={version} copy={copy} background={background}>
|
|
81
|
+
<AuthWrapper name={name} version={version} copy={copy} logo={logo} background={background}>
|
|
82
82
|
<Grid container spacing={3}>
|
|
83
83
|
<Grid item xs={12}>
|
|
84
84
|
<Stack direction="row" justifyContent="space-between" alignItems="baseline" sx={{ mb: { xs: -0.5, sm: 0.5 } }}>
|
|
@@ -10,7 +10,7 @@ import { Link } from 'react-router-dom';
|
|
|
10
10
|
import PropTypes from 'prop-types';
|
|
11
11
|
import { TextInput } from '../ra-inputs';
|
|
12
12
|
|
|
13
|
-
const RecoverPage = ({ name, copy, version, background }: BaseAuthProps) => {
|
|
13
|
+
const RecoverPage = ({ name, copy, logo, version, background }: BaseAuthProps) => {
|
|
14
14
|
const [loading, setLoading] = useState(false);
|
|
15
15
|
const translate = useTranslate();
|
|
16
16
|
const redirect = useRedirect();
|
|
@@ -34,7 +34,7 @@ const RecoverPage = ({ name, copy, version, background }: BaseAuthProps) => {
|
|
|
34
34
|
.finally(() => setLoading(false));
|
|
35
35
|
};
|
|
36
36
|
return (
|
|
37
|
-
<AuthWrapper name={name} copy={copy} version={version} background={background}>
|
|
37
|
+
<AuthWrapper name={name} copy={copy} logo={logo} version={version} background={background}>
|
|
38
38
|
<Grid container spacing={3}>
|
|
39
39
|
<Grid item xs={12}>
|
|
40
40
|
<Stack direction="row" justifyContent="space-between" alignItems="baseline" sx={{ mb: { xs: -0.5, sm: 0.5 } }}>
|
|
@@ -10,7 +10,7 @@ import PropTypes from 'prop-types';
|
|
|
10
10
|
import { TextInput } from '../ra-inputs';
|
|
11
11
|
import { useState } from 'react';
|
|
12
12
|
|
|
13
|
-
const RegisterPage = ({ name, copy, version }: BaseAuthProps) => {
|
|
13
|
+
const RegisterPage = ({ name, copy, version, logo, background }: BaseAuthProps) => {
|
|
14
14
|
const [loading, setLoading] = useState(false);
|
|
15
15
|
const translate = useTranslate();
|
|
16
16
|
const redirect = useRedirect();
|
|
@@ -34,7 +34,7 @@ const RegisterPage = ({ name, copy, version }: BaseAuthProps) => {
|
|
|
34
34
|
.finally(() => setLoading(false));
|
|
35
35
|
};
|
|
36
36
|
return (
|
|
37
|
-
<AuthWrapper name={name} copy={copy} version={version}>
|
|
37
|
+
<AuthWrapper name={name} copy={copy} logo={logo} background={background} version={version}>
|
|
38
38
|
<Grid container spacing={3}>
|
|
39
39
|
<Grid item xs={12}>
|
|
40
40
|
<Stack direction="row" justifyContent="space-between" alignItems="baseline" sx={{ mb: { xs: -0.5, sm: 0.5 } }}>
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
export type BaseAuthProps = {
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Application version to show.
|
|
4
4
|
*/
|
|
5
5
|
version: string;
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Application name to show.
|
|
8
8
|
*/
|
|
9
9
|
name: string;
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* Copy to show.
|
|
12
12
|
*/
|
|
13
13
|
copy?: string;
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
16
|
-
|
|
15
|
+
* Indicates top left logo to display in the login page.
|
|
16
|
+
*/
|
|
17
|
+
logo?: React.ReactNode;
|
|
18
|
+
/**
|
|
19
|
+
* Background image to show.
|
|
17
20
|
*
|
|
18
21
|
* @see AuthBackground per maggiori informazioni.
|
|
19
22
|
* @example
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import * as yup from 'yup';
|
|
4
|
+
import { LocalStorage } from '../utils/localStorage';
|
|
5
|
+
|
|
6
|
+
const localStorageInstance = new LocalStorage();
|
|
7
|
+
|
|
8
|
+
function useLocalStorage(key: string, defaultValue?: unknown): [unknown, (newValue: unknown) => unknown] {
|
|
9
|
+
yup.string().required().validateSync(key);
|
|
10
|
+
|
|
11
|
+
function buildSetter(key: string, currentValue: unknown, callback: (newValue: unknown) => unknown): (newValue: unknown) => unknown {
|
|
12
|
+
return function (newValue: unknown) {
|
|
13
|
+
const updatedValue = _.isFunction(newValue) ? newValue(currentValue) : newValue;
|
|
14
|
+
localStorageInstance.set(key, updatedValue);
|
|
15
|
+
callback(updatedValue);
|
|
16
|
+
return updatedValue;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const [fallbackValue, updateFallbackValue] = useState(defaultValue),
|
|
21
|
+
[state, updateState] = useState(localStorageInstance.get(key) ?? defaultValue),
|
|
22
|
+
[setter, updateSetter] = useState(() => buildSetter(key, state, updateState));
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (!_.isEqual(defaultValue, fallbackValue)) {
|
|
26
|
+
updateFallbackValue(defaultValue);
|
|
27
|
+
}
|
|
28
|
+
}, [defaultValue, fallbackValue, updateFallbackValue]);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
updateState(localStorageInstance.get(key) ?? fallbackValue);
|
|
32
|
+
}, [key, fallbackValue, updateState]);
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
function listener(value: unknown) {
|
|
36
|
+
updateState(value);
|
|
37
|
+
}
|
|
38
|
+
localStorageInstance.watch(key, listener);
|
|
39
|
+
return () => {
|
|
40
|
+
localStorageInstance.unwatch(key, listener);
|
|
41
|
+
};
|
|
42
|
+
}, [key, updateState]);
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
updateSetter(() => buildSetter(key, state, updateState));
|
|
46
|
+
}, [key, state, updateState, updateSetter]);
|
|
47
|
+
|
|
48
|
+
return [state, setter];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default useLocalStorage;
|
|
@@ -1,13 +1,28 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import { useEffect } from 'react';
|
|
1
|
+
import { OnboardingTip, OnboardingProvider, OnboardingModes, useRestartOnboarding } from '@applica-software-guru/react-admin';
|
|
2
|
+
import { Button, Stack, Typography } from '@mui/material';
|
|
4
3
|
|
|
5
4
|
const CustomPage = () => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
return (
|
|
6
|
+
<OnboardingProvider mode={OnboardingModes.BROWSER}>
|
|
7
|
+
<Stack direction="row" justifyContent="space-between">
|
|
8
|
+
<OnboardingTip title="Test 1" id="test1">
|
|
9
|
+
<Typography>Test 1</Typography>
|
|
10
|
+
</OnboardingTip>
|
|
11
|
+
<OnboardingTip title="Test 2" id="test2">
|
|
12
|
+
<Typography>Test 2</Typography>
|
|
13
|
+
</OnboardingTip>
|
|
14
|
+
<OnboardingTip title="Test 3" id="test3" step={1}>
|
|
15
|
+
<Typography>Test 3</Typography>
|
|
16
|
+
</OnboardingTip>
|
|
17
|
+
</Stack>
|
|
18
|
+
<RestartButton />
|
|
19
|
+
</OnboardingProvider>
|
|
20
|
+
);
|
|
11
21
|
};
|
|
12
22
|
|
|
23
|
+
export function RestartButton() {
|
|
24
|
+
const restartOnboarding = useRestartOnboarding();
|
|
25
|
+
return <Button onClick={restartOnboarding}>RESTART</Button>;
|
|
26
|
+
}
|
|
27
|
+
|
|
13
28
|
export default CustomPage;
|
package/src/utils/index.ts
CHANGED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import * as yup from 'yup';
|
|
3
|
+
|
|
4
|
+
const listenerSchema = yup.mixed().test('function', 'Invalid function', (value) => {
|
|
5
|
+
return _.isFunction(value);
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
type IListener = (value: unknown) => void;
|
|
9
|
+
interface ILocalStorage {
|
|
10
|
+
get: (key: string) => unknown | null;
|
|
11
|
+
set: (key: string, value: unknown) => unknown | null;
|
|
12
|
+
watch: (key: string, callback: IListener) => void;
|
|
13
|
+
unwatch: (key: string, callback?: IListener) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class LocalStorage implements ILocalStorage {
|
|
17
|
+
#listeners: Array<{ key: string; callback: IListener }> = [];
|
|
18
|
+
|
|
19
|
+
#canAccessLocalStorage(): boolean {
|
|
20
|
+
return typeof window !== 'undefined';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get(key: string): unknown | null {
|
|
24
|
+
yup.string().required().validateSync(key);
|
|
25
|
+
if (!this.#canAccessLocalStorage()) {
|
|
26
|
+
return null;
|
|
27
|
+
} else {
|
|
28
|
+
const value = localStorage.getItem(key);
|
|
29
|
+
return _.isNil(value) ? null : JSON.parse(value);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
set(key: string, value: unknown): unknown | null {
|
|
34
|
+
yup.string().required().validateSync(key);
|
|
35
|
+
if (this.#canAccessLocalStorage()) {
|
|
36
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
37
|
+
return value;
|
|
38
|
+
} else {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
watch(key: string, callback: IListener): void {
|
|
44
|
+
yup.string().required().validateSync(key);
|
|
45
|
+
listenerSchema.validateSync(callback);
|
|
46
|
+
if (this.#canAccessLocalStorage()) {
|
|
47
|
+
this.#listeners.push({ key: key, callback: callback });
|
|
48
|
+
window.addEventListener('storage', (e) => {
|
|
49
|
+
if (e.storageArea === localStorage && e.key === key) {
|
|
50
|
+
callback(this.get(key));
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
unwatch(key: string, callback?: IListener | undefined): void {
|
|
57
|
+
yup.string().required().validateSync(key);
|
|
58
|
+
listenerSchema.optional().default(_.identity).validateSync(callback);
|
|
59
|
+
if (this.#canAccessLocalStorage()) {
|
|
60
|
+
if (callback !== undefined) {
|
|
61
|
+
this.#listeners = _.reject(this.#listeners, (listener) => {
|
|
62
|
+
return listener.key === key && listener.callback === callback;
|
|
63
|
+
});
|
|
64
|
+
} else {
|
|
65
|
+
this.#listeners = _.reject(this.#listeners, (listener) => {
|
|
66
|
+
return listener.key === key;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export { LocalStorage };
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react';
|
|
2
|
-
|
|
3
|
-
export default function useLocalStorage(key, defaultValue) {
|
|
4
|
-
const [value, setValue] = useState(() => {
|
|
5
|
-
const storedValue = typeof window !== 'undefined' ? localStorage.getItem(key) : null;
|
|
6
|
-
return storedValue === null ? defaultValue : JSON.parse(storedValue);
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
useEffect(() => {
|
|
10
|
-
const listener = (e) => {
|
|
11
|
-
if (typeof window !== 'undefined' && e.storageArea === localStorage && e.key === key) {
|
|
12
|
-
setValue(e.newValue ? JSON.parse(e.newValue) : e.newValue);
|
|
13
|
-
}
|
|
14
|
-
};
|
|
15
|
-
window.addEventListener('storage', listener);
|
|
16
|
-
|
|
17
|
-
return () => {
|
|
18
|
-
window.removeEventListener('storage', listener);
|
|
19
|
-
};
|
|
20
|
-
}, [key, defaultValue]);
|
|
21
|
-
|
|
22
|
-
const setValueInLocalStorage = (newValue) => {
|
|
23
|
-
setValue((currentValue) => {
|
|
24
|
-
const result = typeof newValue === 'function' ? newValue(currentValue) : newValue;
|
|
25
|
-
if (typeof window !== 'undefined') localStorage.setItem(key, JSON.stringify(result));
|
|
26
|
-
return result;
|
|
27
|
-
});
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
return [value, setValueInLocalStorage];
|
|
31
|
-
}
|
|
File without changes
|