@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.
Files changed (71) hide show
  1. package/dist/ApplicaAdmin.d.ts +43 -36
  2. package/dist/ApplicaAdmin.d.ts.map +1 -1
  3. package/dist/components/AuthWrapper.d.ts +20 -17
  4. package/dist/components/AuthWrapper.d.ts.map +1 -1
  5. package/dist/components/MainIcon.d.ts +4 -9
  6. package/dist/components/MainIcon.d.ts.map +1 -1
  7. package/dist/components/Onboarding/OnboardingTip.d.ts +5 -0
  8. package/dist/components/Onboarding/OnboardingTip.d.ts.map +1 -0
  9. package/dist/components/Onboarding/Provider.d.ts +49 -0
  10. package/dist/components/Onboarding/Provider.d.ts.map +1 -0
  11. package/dist/components/Onboarding/enums.d.ts +7 -0
  12. package/dist/components/Onboarding/enums.d.ts.map +1 -0
  13. package/dist/components/Onboarding/hooks.d.ts +10 -0
  14. package/dist/components/Onboarding/hooks.d.ts.map +1 -0
  15. package/dist/components/Onboarding/index.d.ts +7 -0
  16. package/dist/components/Onboarding/index.d.ts.map +1 -0
  17. package/dist/components/Onboarding/onboardingDataProvider.d.ts +8 -0
  18. package/dist/components/Onboarding/onboardingDataProvider.d.ts.map +1 -0
  19. package/dist/components/Onboarding/schemas.d.ts +5 -0
  20. package/dist/components/Onboarding/schemas.d.ts.map +1 -0
  21. package/dist/components/Onboarding/types.d.ts +8 -0
  22. package/dist/components/Onboarding/types.d.ts.map +1 -0
  23. package/dist/components/index.d.ts +1 -0
  24. package/dist/components/ra-pages/ActivatePage.d.ts +1 -1
  25. package/dist/components/ra-pages/ActivatePage.d.ts.map +1 -1
  26. package/dist/components/ra-pages/LoginPage.d.ts +11 -11
  27. package/dist/components/ra-pages/LoginPage.d.ts.map +1 -1
  28. package/dist/components/ra-pages/RecoverPage.d.ts +1 -1
  29. package/dist/components/ra-pages/RecoverPage.d.ts.map +1 -1
  30. package/dist/components/ra-pages/RegisterPage.d.ts +1 -1
  31. package/dist/components/ra-pages/RegisterPage.d.ts.map +1 -1
  32. package/dist/components/ra-pages/types.d.ts +8 -5
  33. package/dist/components/ra-pages/types.d.ts.map +1 -1
  34. package/dist/hooks/index.d.ts +14 -14
  35. package/dist/hooks/index.d.ts.map +1 -1
  36. package/dist/hooks/useLocalStorage.d.ts +2 -1
  37. package/dist/hooks/useLocalStorage.d.ts.map +1 -1
  38. package/dist/react-admin.cjs.js +70 -67
  39. package/dist/react-admin.cjs.js.map +1 -1
  40. package/dist/react-admin.es.js +13841 -12034
  41. package/dist/react-admin.es.js.map +1 -1
  42. package/dist/react-admin.umd.js +71 -68
  43. package/dist/react-admin.umd.js.map +1 -1
  44. package/dist/utils/index.d.ts +1 -0
  45. package/dist/utils/index.d.ts.map +1 -1
  46. package/dist/utils/localStorage.d.ts +16 -0
  47. package/dist/utils/localStorage.d.ts.map +1 -0
  48. package/package.json +3 -2
  49. package/src/ApplicaAdmin.tsx +46 -36
  50. package/src/components/{AuthWrapper.jsx → AuthWrapper.tsx} +13 -4
  51. package/src/components/{MainIcon.jsx → MainIcon.tsx} +4 -8
  52. package/src/components/Onboarding/OnboardingTip.tsx +52 -0
  53. package/src/components/Onboarding/Provider.tsx +145 -0
  54. package/src/components/Onboarding/enums.ts +7 -0
  55. package/src/components/Onboarding/hooks.tsx +80 -0
  56. package/src/components/Onboarding/index.ts +6 -0
  57. package/src/components/Onboarding/onboardingDataProvider.tsx +128 -0
  58. package/src/components/Onboarding/schemas.ts +6 -0
  59. package/src/components/Onboarding/types.ts +8 -0
  60. package/src/components/index.jsx +1 -0
  61. package/src/components/ra-pages/ActivatePage.tsx +2 -2
  62. package/src/components/ra-pages/LoginPage.tsx +12 -12
  63. package/src/components/ra-pages/RecoverPage.tsx +2 -2
  64. package/src/components/ra-pages/RegisterPage.tsx +2 -2
  65. package/src/components/ra-pages/types.ts +8 -5
  66. package/src/hooks/useLocalStorage.tsx +51 -0
  67. package/src/playground/components/pages/CustomPage.jsx +23 -8
  68. package/src/utils/index.ts +1 -0
  69. package/src/utils/localStorage.ts +73 -0
  70. package/src/hooks/useLocalStorage.jsx +0 -31
  71. /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 };
@@ -0,0 +1,6 @@
1
+ import * as yup from 'yup';
2
+ import { OnboardingModes } from './enums';
3
+ const onboardingTopicSchema = yup.string().required(),
4
+ onboardingModesSchema = yup.string().oneOf(Object.values(OnboardingModes));
5
+
6
+ export { onboardingModesSchema, onboardingTopicSchema };
@@ -0,0 +1,8 @@
1
+ type ITipDescriptor = {
2
+ id: string;
3
+ step: number;
4
+ dismissed?: boolean;
5
+ completeOnboarding?: boolean;
6
+ };
7
+
8
+ export type { ITipDescriptor };
@@ -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
- * Indica se abilitare o meno la schermata di recupero password.
15
- * Se abilitato è opportuno registrare una rotta a /recover con un componente
16
- * che si occupi del recupero, puoi utilizzare direttamente il componente di Applica
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 per maggiori informazioni.
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
- * Indica se abilitare o meno la schermata di registrazione.
28
- * Se abilitato è opportuno registrare una rotta a /register con un componente
29
- * che si occupi della registrazione, puoi utilizzare direttamente il componente di Applica
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 per maggiori informazioni.
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
- * Indica la pagina a cui reindirizzare l'utente dopo il login.
41
- * Se non specificata viene utilizzata la home page di react-admin.
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
- * Versione dell'applicazione da mostrare.
3
+ * Application version to show.
4
4
  */
5
5
  version: string;
6
6
  /**
7
- * Nome dell'applicazione da mostrare.
7
+ * Application name to show.
8
8
  */
9
9
  name: string;
10
10
  /**
11
- * Copyright da mostrare nella pagina.
11
+ * Copy to show.
12
12
  */
13
13
  copy?: string;
14
14
  /**
15
- * Immagine di sfondo della pagina.
16
- * Se non specificata viene utilizzata l'immagine di default.
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 { Typography } from '@mui/material';
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
- useEffect(() => {
7
- throw new Error('Custom Page not implemented yet');
8
- }, []);
9
-
10
- return <Typography>Hello, Custom Page!</Typography>;
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;
@@ -1,3 +1,4 @@
1
1
  export * from './time';
2
2
  export * from './lang';
3
3
  export * from './localizedValue';
4
+ export * from './localStorage';
@@ -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