@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
@@ -1,4 +1,5 @@
1
1
  export * from './time';
2
2
  export * from './lang';
3
3
  export * from './localizedValue';
4
+ export * from './localStorage';
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,QAAQ,CAAC;AACvB,cAAc,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,QAAQ,CAAC;AACvB,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC"}
@@ -0,0 +1,16 @@
1
+ type IListener = (value: unknown) => void;
2
+ interface ILocalStorage {
3
+ get: (key: string) => unknown | null;
4
+ set: (key: string, value: unknown) => unknown | null;
5
+ watch: (key: string, callback: IListener) => void;
6
+ unwatch: (key: string, callback?: IListener) => void;
7
+ }
8
+ declare class LocalStorage implements ILocalStorage {
9
+ #private;
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 | undefined): void;
14
+ }
15
+ export { LocalStorage };
16
+ //# sourceMappingURL=localStorage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"localStorage.d.ts","sourceRoot":"","sources":["../../../src/utils/localStorage.ts"],"names":[],"mappings":"AAOA,KAAK,SAAS,GAAG,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;AAC1C,UAAU,aAAa;IACrB,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI,CAAC;IACrC,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,OAAO,GAAG,IAAI,CAAC;IACrD,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,KAAK,IAAI,CAAC;IAClD,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,KAAK,IAAI,CAAC;CACtD;AAED,cAAM,YAAa,YAAW,aAAa;;IAOzC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI;IAUhC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,IAAI;IAUhD,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,GAAG,IAAI;IAa7C,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,IAAI;CAe7D;AAED,OAAO,EAAE,YAAY,EAAE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applica-software-guru/react-admin",
3
- "version": "1.3.163",
3
+ "version": "1.3.166",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -68,7 +68,8 @@
68
68
  "react-router-dom": "^6.1.0",
69
69
  "react-sticky-box": "2.0.4",
70
70
  "simplebar": "6.2.5",
71
- "simplebar-react": "3.2.4"
71
+ "simplebar-react": "3.2.4",
72
+ "yup": "^1.3.3"
72
73
  },
73
74
  "devDependencies": {
74
75
  "@applica-software-guru/crud-client": "^1.1",
@@ -21,98 +21,98 @@ const defaultQueryClient = new QueryClient({
21
21
 
22
22
  export type ApplicaAdminProps = AdminProps & {
23
23
  /**
24
- * Eventuali configurazioni aggiuntive da passare al tema.
25
- * @remarks Questo tema è basato su Mantis Theme (https://mantisdashboard.io/)
24
+ * Optionally, a custom theme to use in the application.
25
+ * @remarks This theme is based on Mantis Theme (https://mantisdashboard.io/)
26
26
  *
27
27
  * @see https://marmelab.com/react-admin/Theming.html
28
28
  * @see https://material-ui.com/customization/theming/
29
29
  */
30
30
  theme: any;
31
31
  /**
32
- * Consente di personalizzare ulteriormente il tema in base alle specifiche esposte dal tema stesso.
33
- * Divertiti con cautela a modificare variabili di configurazione del tema, se fai un casino Marco Colucci potrebbe usare l'estintore (ho detto tutto).
32
+ * Optionally, a custom theme configuration to use in the application.
33
+ * Have fun with caution to modify theme configuration variables, if you make a mess Marco Colucci could use the fire extinguisher (I said everything).
34
34
  */
35
35
  themeConfig: ThemeConfig;
36
36
  /**
37
- * URL dell'API da utilizzare nell'applicazione.
38
- * Sebbene venga già definito un dataProvider ed authProvider di default, è possibile che l'app
39
- * utilizzi l'apiUrl specificato per ulteriori operazioni (es. getstione delle notifiche).
37
+ * The URL of the API to use in the application.
38
+ * Although a default dataProvider and authProvider are already defined, it is possible that the app
39
+ * uses the specified apiUrl for further operations (e.g. handling notifications).
40
40
  */
41
41
  apiUrl: string;
42
42
  /**
43
- * La lingua di default da utilizzare nell'applicazione.
43
+ * The default language to use in the application (default: en).
44
44
  */
45
45
  defaultLocale: string;
46
46
  /**
47
- * Se impostato a true, l'applicazione lavora in modalità sviluppo.
48
- * La modalità sviluppo è utile per effettuare test e debug ed esegue attività che non sono
49
- * consigliate in produzione (es. cattura e inoltro dei messaggi non localizzati).
47
+ * If set to true, the application works in development mode.
48
+ * Development mode is useful for testing and debugging and performs activities that are not
49
+ * recommended in production (e.g. capturing and forwarding non-localized messages).
50
50
  */
51
51
  development: boolean;
52
52
  /**
53
- * Il logo da visualizzare nella barra laterale quando il menu è espanso lateralmente.
53
+ * The main logo to display in the sidebar when the menu is expanded laterally.
54
54
  */
55
55
  logoMain: any;
56
56
  /**
57
- * Eventuale logo da visualizzare nella barra laterale quando il menu è ridotto lateralmente.
57
+ * The icon to display in the sidebar when the menu is collapsed laterally.
58
58
  */
59
59
  logoIcon: any;
60
60
  /**
61
- * Configurazione del menu da visualizzare nell'applicazione.
61
+ * Configured menu to display in the application.
62
62
  */
63
63
  menu: MenuProps;
64
64
  /**
65
- * Nome dell'applicazione da mostrare nell'header.
65
+ * The name of the application to display in the header.
66
66
  */
67
67
  name: string;
68
68
  /**
69
- * Testo da mostrare nel footer. Default: '© Applica Software Guru for'.
69
+ * Text to display in the footer. Default: '© Applica Software Guru for'.
70
70
  */
71
71
  copy: string;
72
72
  /**
73
- * Versione dell'applicazione da mostrare nel footer.
73
+ * Application version to display in the footer.
74
74
  */
75
75
  version: string;
76
76
  /**
77
- * Il provider di autenticazione da utilizzare nell'applicazione.
77
+ * The data provider to use in the application.
78
78
  */
79
79
  dataProvider: DataProvider;
80
80
  /**
81
- * Il provider di dati da utilizzare nell'applicazione.
81
+ * The auth provider to use in the application.
82
82
  */
83
83
  authProvider: AuthProvider;
84
84
  /**
85
- * Indica l'eventuale handler da utilizzare per la gestione degli errori.
86
- * Di default è eseguita una chiamata PUT /api/ui/error con il messaggio di errore "error".
87
- * Puoi implementare un tuo handler per gestire gli errori in modo diverso.
85
+ * Indicates the handler to use for error management.
86
+ * By default, a PUT /api/ui/error call is made with the error message "error".
87
+ * You can implement your own handler to manage errors differently.
88
88
  */
89
89
  errorHandler?: IErrorEventHandler | undefined;
90
90
  /**
91
- * Indica il componente da visualizzare in caso di errore.
91
+ * Indicates the component to display in case of error.
92
92
  */
93
93
  error: React.Component;
94
94
  /**
95
- * Indica il nome della risorsa REST da utilizzare per la gestione delle notifiche.
95
+ * Indicates the name of the REST resource to use for notification management.
96
96
  * @default "entities/notification"
97
97
  * @example
98
- * // In questo caso, le notifiche verranno gestite tramite la risorsa "entities/notification"
98
+ * // In this case, notifications will be managed through the "entities/notification" resource
99
99
  * <ApplicaAdmin notification="entities/notification" />
100
100
  */
101
101
  notification: string;
102
102
  /**
103
- * Indica se le notifiche devono essere disabilitate.
104
- * Se le notifiche sono abilitate comparirà automaticamente un'icona in alto a destra nell'header.
103
+ * Indicates whether notifications should be disabled.
104
+ * If notifications are enabled, an icon will automatically appear in the top right of the header.
105
105
  *
106
106
  * @example
107
107
  * <ApplicaAdmin enableNotification />
108
108
  */
109
109
  enableNotification: boolean;
110
110
  /**
111
- * Indica se la schermata di registrazione deve essere disabilitata.
112
- * Se abilitata è necessario registrare una pagina, nelle rotte, che punti a /register
111
+ * Indicates whether the registration screen should be disabled.
112
+ * If enabled, it is necessary to register a page, in the routes, that points to /register
113
113
  *
114
114
  * @example
115
- * // Pagina di base realizzata da Applica
115
+ * // Basic page made by Applica
116
116
  * import { RegisterPage } from "@applica-software-guru/react-admin";
117
117
  * <CustomRoutes noLayout>
118
118
  * <Route path="/register" component={RegisterPage} />
@@ -120,11 +120,11 @@ export type ApplicaAdminProps = AdminProps & {
120
120
  */
121
121
  enableRegistration: boolean;
122
122
  /**
123
- * Indica se la schermata di recupero password deve essere disabilitata.
124
- * Se abilitata è necessario registrare una pagina, nelle rotte, che punti a /recover
123
+ * Indicates whether the password recovery screen should be disabled.
124
+ * If enabled, it is necessary to register a page, in the routes, that points to /recover
125
125
  *
126
126
  * @example
127
- * // Pagina di base realizzata da Applica
127
+ * // Basic page made by Applica
128
128
  * import { RecoverPage } from "@applica-software-guru/react-admin";
129
129
  * <CustomRoutes noLayout>
130
130
  * <Route path="/recover" component={RecoverPage} />
@@ -132,13 +132,20 @@ export type ApplicaAdminProps = AdminProps & {
132
132
  */
133
133
  enablePasswordRecover: boolean;
134
134
  /**
135
- * L'instanza di QueryClient da utilizzare nell'applicazione
135
+ * The query client instance to use in the application.
136
136
  */
137
137
  queryClient: QueryClient;
138
+ /**
139
+ * Background image to display in login, recover, and register pages.
140
+ * This image must be a component with absolute positioning.
141
+ */
142
+ background?: React.ReactNode;
138
143
  };
139
144
 
140
145
  /**
141
- * Definisce un'applicazione super figa basata su React Admin, Mantis Theme ed il nostro stile.
146
+ * Define a basic super cool application based on React Admin, Mantis Theme and our style.
147
+ *
148
+ * @author A-Team - The Applica Software Guru
142
149
  * @param {ApplicaAdminProps}
143
150
  * @returns {React.ReactElement}
144
151
  */
@@ -164,6 +171,7 @@ const ApplicaAdmin = ({
164
171
  enableRegistration,
165
172
  enablePasswordRecover,
166
173
  queryClient,
174
+ background,
167
175
  ...props
168
176
  }: ApplicaAdminProps) => {
169
177
  useErrorEventCatcher({
@@ -196,13 +204,15 @@ const ApplicaAdmin = ({
196
204
  // @ts-ignore
197
205
  name,
198
206
  copy,
207
+ logo: logoMain,
199
208
  version,
209
+ background,
200
210
  enableRegistration,
201
211
  enablePasswordRecover
202
212
  });
203
213
  }
204
214
  return loginPage;
205
- }, [loginPage, name, version, copy, enableRegistration, enablePasswordRecover]);
215
+ }, [loginPage, name, version, copy, background, logoMain, enableRegistration, enablePasswordRecover]);
206
216
  const layout = useMemo(
207
217
  () => (props: any) => {
208
218
  const _logoMain = name ? <MainIcon title={name} /> : logoMain;
@@ -7,10 +7,19 @@ import MainIcon from './MainIcon';
7
7
  import PropTypes from 'prop-types';
8
8
  import React from 'react';
9
9
 
10
- const AuthWrapper = ({ version, name, copy, children, background = AuthBackground }) => {
10
+ export type AuthWrapperProps = {
11
+ version: string;
12
+ name: string;
13
+ copy?: string;
14
+ background?: React.ReactNode | React.ComponentType;
15
+ children: React.ReactNode;
16
+ logo?: React.ReactNode;
17
+ };
18
+
19
+ const AuthWrapper = ({ version, name, copy, children, logo, background = AuthBackground }: AuthWrapperProps) => {
11
20
  return (
12
21
  <Box sx={{ minHeight: '100vh' }}>
13
- {React.isValidElement(background) ? background : React.createElement(background)}
22
+ {React.isValidElement(background) ? background : React.createElement(background as any)}
14
23
  <Grid
15
24
  container
16
25
  direction="column"
@@ -19,8 +28,8 @@ const AuthWrapper = ({ version, name, copy, children, background = AuthBackgroun
19
28
  minHeight: '100vh'
20
29
  }}
21
30
  >
22
- <Grid item xs={12} sx={{ ml: 3, mt: 3 }}>
23
- <MainIcon title={name} />
31
+ <Grid item xs={12} sx={{ ml: 3 }}>
32
+ {logo ? logo : <MainIcon title={name} />}
24
33
  </Grid>
25
34
  <Grid item xs={12}>
26
35
  <Grid
@@ -1,8 +1,8 @@
1
1
  import { Stack, Typography } from '@mui/material';
2
-
3
- import PropTypes from 'prop-types';
4
-
5
- const MainIcon = ({ title }) => (
2
+ export type MainIconProps = {
3
+ title: string;
4
+ };
5
+ const MainIcon = ({ title }: MainIconProps) => (
6
6
  <Stack direction="row">
7
7
  <Typography variant="h2" sx={{ fontWeight: 'regular', letterSpacing: -2.5 }}>
8
8
  a.
@@ -14,8 +14,4 @@ const MainIcon = ({ title }) => (
14
14
  </Stack>
15
15
  );
16
16
 
17
- MainIcon.propTypes = {
18
- title: PropTypes.string.isRequired
19
- };
20
-
21
17
  export default MainIcon;
@@ -0,0 +1,52 @@
1
+ import _ from 'lodash';
2
+ import { ITipDescriptor } from './types';
3
+ import { useOnboardingTip } from './hooks';
4
+ import { Optional } from 'src/types';
5
+ import { Button, Stack, TooltipProps, Typography, buttonClasses, styled, tooltipClasses } from '@mui/material';
6
+ import { Tooltip } from '../@extended';
7
+
8
+ type IOnboardingTipProps = TooltipProps & Optional<Pick<ITipDescriptor, 'id' | 'step' | 'completeOnboarding'>, 'step'>;
9
+
10
+ function BaseOnboardingTip(props: IOnboardingTipProps) {
11
+ const { id, step = 0, title, completeOnboarding } = props,
12
+ { tip, dismiss } = useOnboardingTip({ id: id, step: step, completeOnboarding: completeOnboarding }),
13
+ { open } = tip,
14
+ passProps = _.omit(props, ['id', 'step', 'title', 'completeOnboarding']);
15
+ return (
16
+ <Tooltip
17
+ {...passProps}
18
+ arrow={true}
19
+ open={open}
20
+ title={
21
+ <Stack gap={1}>
22
+ <Typography>{title}</Typography>
23
+ <Button onClick={dismiss} variant="text" color="primary" size="small">
24
+ Got it!
25
+ </Button>
26
+ </Stack>
27
+ }
28
+ >
29
+ {props.children}
30
+ </Tooltip>
31
+ );
32
+ }
33
+
34
+ const OnboardingTip = styled(BaseOnboardingTip)(({ theme }) => {
35
+ const { palette, spacing } = theme,
36
+ { primary, background } = palette;
37
+ return {
38
+ [`& .${tooltipClasses.tooltip}`]: {
39
+ padding: `${spacing(1)} ${spacing(1.5)}`,
40
+ backgroundColor: primary.main,
41
+ color: primary.contrastText,
42
+ [`& .${buttonClasses.root}`]: {
43
+ backgroundColor: background.paper
44
+ }
45
+ },
46
+ [`& .${tooltipClasses.arrow}`]: {
47
+ color: primary.main
48
+ }
49
+ };
50
+ });
51
+
52
+ export { OnboardingTip };
@@ -0,0 +1,145 @@
1
+ import _ from 'lodash';
2
+ import { PropsWithChildren, createContext, useEffect, useMemo, useReducer } from 'react';
3
+ import { OnboardingModes } from './enums';
4
+ import { useOnboardingDataProvider } from './onboardingDataProvider';
5
+ import { ITipDescriptor } from './types';
6
+
7
+ enum OnboardingActionType {
8
+ RESTART = 'RESTART',
9
+ SET_SHOW = 'SET_SHOW',
10
+ REGISTER_TIP = 'REGISTER_TIP',
11
+ UNREGISTER_TIP = 'UNREGISTER_TIP',
12
+ DISMISS_TIP = 'DISMISS_TIP',
13
+ SET_ON_COMPLETE = 'SET_ON_COMPLETE'
14
+ }
15
+
16
+ type IOnboardingProviderProps = PropsWithChildren<{
17
+ mode: OnboardingModes;
18
+ topic?: string;
19
+ }>;
20
+ type IOnCompleteCallback = () => void;
21
+ type IOnboardingState = {
22
+ show: boolean;
23
+ tips: Array<ITipDescriptor>;
24
+ currentStep: number;
25
+ onComplete: IOnCompleteCallback;
26
+ };
27
+ type IOnboardingAction =
28
+ | { type: OnboardingActionType.RESTART }
29
+ | { type: OnboardingActionType.SET_SHOW; payload: boolean }
30
+ | { type: OnboardingActionType.REGISTER_TIP; payload: ITipDescriptor }
31
+ | { type: OnboardingActionType.UNREGISTER_TIP; payload: ITipDescriptor | string }
32
+ | { type: OnboardingActionType.DISMISS_TIP; payload: ITipDescriptor | string }
33
+ | { type: OnboardingActionType.SET_ON_COMPLETE; payload: IOnCompleteCallback };
34
+ type IOnboardingContext = { state: IOnboardingState; dispatch: (action: IOnboardingAction) => void };
35
+
36
+ function reducer(state: IOnboardingState, action: IOnboardingAction): IOnboardingState {
37
+ const newState = _.clone(state),
38
+ { type } = action;
39
+
40
+ switch (type) {
41
+ case OnboardingActionType.SET_SHOW: {
42
+ const { payload } = action;
43
+ return _.extend(newState, { show: payload });
44
+ }
45
+ case OnboardingActionType.REGISTER_TIP: {
46
+ const { payload } = action,
47
+ { id } = payload,
48
+ tips = _.chain(newState.tips)
49
+ .clone()
50
+ .reject((t) => t.id === id)
51
+ .value(),
52
+ existingTip = _.find(newState.tips, { id: id }),
53
+ newTip = _.chain(existingTip ?? {})
54
+ .clone()
55
+ .extend(payload)
56
+ .value();
57
+
58
+ tips.push(newTip);
59
+ return _.extend(newState, { tips: _.sortBy(tips, ['step']) });
60
+ }
61
+ case OnboardingActionType.UNREGISTER_TIP: {
62
+ const { payload } = action,
63
+ id = _.isString(payload) ? payload : payload.id,
64
+ tips = _.chain(newState.tips)
65
+ .reject((t) => t.id === id)
66
+ .sortBy(['step'])
67
+ .value();
68
+ return _.extend(newState, { tips: tips });
69
+ }
70
+ case OnboardingActionType.DISMISS_TIP: {
71
+ const { payload } = action,
72
+ id = _.isString(payload) ? payload : payload.id,
73
+ tips = _.chain(newState.tips)
74
+ .reject((t) => t.id === id)
75
+ .value(),
76
+ tip = _.find(newState.tips, { id: id });
77
+
78
+ if (_.isNil(tip)) {
79
+ return newState;
80
+ }
81
+
82
+ const newTip = _.chain(tip).clone().extend({ dismissed: true }).value();
83
+ tips.push(newTip);
84
+ const updatedStep =
85
+ _.chain(tips)
86
+ .reject((t) => t.dismissed ?? false)
87
+ .minBy('step')
88
+ .value()?.step ?? 0,
89
+ isOnboardingComplete =
90
+ newTip?.completeOnboarding &&
91
+ _.chain(tips)
92
+ .reject((t) => t.step < updatedStep)
93
+ .reject((t) => t.dismissed ?? false)
94
+ .isEmpty()
95
+ .value();
96
+
97
+ if (isOnboardingComplete) {
98
+ state.onComplete();
99
+ }
100
+
101
+ return _.extend(newState, { tips: _.sortBy(tips, ['step']), currentStep: updatedStep });
102
+ }
103
+ case OnboardingActionType.SET_ON_COMPLETE: {
104
+ const { payload } = action;
105
+ return _.extend(newState, { onComplete: payload });
106
+ }
107
+ case OnboardingActionType.RESTART: {
108
+ const tips = _.chain(newState.tips)
109
+ .clone()
110
+ .map((t) => _.omit(t, 'dismissed'))
111
+ .value();
112
+ return _.extend(newState, { tips: tips, currentStep: 0 });
113
+ }
114
+ default:
115
+ return newState;
116
+ }
117
+ }
118
+
119
+ const OnboardingContext = createContext<IOnboardingContext | undefined>(undefined);
120
+
121
+ function OnboardingProvider(props: IOnboardingProviderProps) {
122
+ const { mode, topic = 'default' } = props,
123
+ { onboardingRequired, acknowledgeOnboarding } = useOnboardingDataProvider(mode, topic),
124
+ [state, dispatch] = useReducer(reducer, { show: onboardingRequired, tips: [], currentStep: 0, onComplete: () => {} }),
125
+ value = useMemo(() => ({ state, dispatch }), [state, dispatch]);
126
+
127
+ useEffect(() => {
128
+ dispatch({ type: OnboardingActionType.SET_SHOW, payload: onboardingRequired });
129
+ }, [onboardingRequired, dispatch]);
130
+
131
+ useEffect(() => {
132
+ function onComplete() {
133
+ acknowledgeOnboarding().then(() => {
134
+ dispatch({ type: OnboardingActionType.SET_SHOW, payload: false });
135
+ });
136
+ }
137
+ dispatch({ type: OnboardingActionType.SET_ON_COMPLETE, payload: onComplete });
138
+ }, [acknowledgeOnboarding, dispatch]);
139
+
140
+ return <OnboardingContext.Provider value={value}>{props.children}</OnboardingContext.Provider>;
141
+ }
142
+
143
+ export type { IOnboardingContext, IOnboardingState, IOnboardingAction };
144
+
145
+ export { OnboardingProvider, OnboardingContext, OnboardingActionType };
@@ -0,0 +1,7 @@
1
+ enum OnboardingModes {
2
+ NONE = 'none',
3
+ BROWSER = 'browser',
4
+ USER = 'user'
5
+ }
6
+
7
+ export { OnboardingModes };
@@ -0,0 +1,80 @@
1
+ import _ from 'lodash';
2
+ import { useCallback, useContext, useEffect } from 'react';
3
+ import { ITipDescriptor } from './types';
4
+ import * as yup from 'yup';
5
+ import { IOnboardingAction, IOnboardingContext, IOnboardingState, OnboardingActionType, OnboardingContext } from './Provider';
6
+
7
+ function useOnboardingContext(): IOnboardingContext {
8
+ const context = useContext(OnboardingContext);
9
+ if (context === undefined) {
10
+ throw new Error(
11
+ '[Onboarding] useOnboardingContext: context undefined. Please provide a valid OnboardingContext using OnboardingProvider'
12
+ );
13
+ }
14
+ return context;
15
+ }
16
+
17
+ function useOnboardingState(): IOnboardingState {
18
+ const context = useOnboardingContext();
19
+ return context.state;
20
+ }
21
+
22
+ function useOnboardingDispatch(): React.Dispatch<IOnboardingAction> {
23
+ const context = useOnboardingContext();
24
+ return context.dispatch;
25
+ }
26
+
27
+ function useOnboardingTip(config: ITipDescriptor): {
28
+ tip: ITipDescriptor & { open: boolean };
29
+ dismiss: () => void;
30
+ } {
31
+ yup.object({ id: yup.string().required(), step: yup.number().required() }).validateSync(config);
32
+
33
+ const { id, step, completeOnboarding } = config,
34
+ { show, tips, currentStep } = useOnboardingState(),
35
+ tip = _.find(tips, { id: id }),
36
+ { dismissed } = tip ?? {},
37
+ dispatch = useOnboardingDispatch(),
38
+ dismiss = useCallback(() => {
39
+ dispatch({
40
+ type: OnboardingActionType.DISMISS_TIP,
41
+ payload: id
42
+ });
43
+ }, [dispatch, id]);
44
+
45
+ useEffect(() => {
46
+ if (_.isNil(tip) || step !== tip?.step) {
47
+ dispatch({
48
+ type: OnboardingActionType.REGISTER_TIP,
49
+ payload: {
50
+ id: id,
51
+ step: step,
52
+ completeOnboarding: completeOnboarding
53
+ }
54
+ });
55
+ }
56
+ }, [id, step, completeOnboarding, tip]);
57
+
58
+ useEffect(() => {
59
+ return () => {
60
+ dispatch({ type: OnboardingActionType.UNREGISTER_TIP, payload: id });
61
+ };
62
+ }, [id, dispatch]);
63
+
64
+ return {
65
+ tip: _.chain(tip ?? config)
66
+ .clone()
67
+ .extend({ open: show && !_.isNil(tip) && currentStep === step && !dismissed })
68
+ .value(),
69
+ dismiss: dismiss
70
+ };
71
+ }
72
+
73
+ function useRestartOnboarding(): () => void {
74
+ const dispatch = useOnboardingDispatch();
75
+ return useCallback(() => {
76
+ dispatch({ type: OnboardingActionType.RESTART });
77
+ }, [dispatch]);
78
+ }
79
+
80
+ export { useOnboardingTip, useRestartOnboarding };
@@ -0,0 +1,6 @@
1
+ export * from './enums';
2
+ export * from './hooks';
3
+ export * from './onboardingDataProvider';
4
+ export * from './OnboardingTip';
5
+ export * from './Provider';
6
+ export * from './schemas';