@brightlayer-ui/react-native-template-authentication-typescript 2.0.0-beta.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,63 @@
1
+ # Changelog
2
+
3
+ ## v2.0.0 (Not yet published)
4
+
5
+ ### Changed
6
+
7
+ - Changed package namespace from `@pxblue` to `@brightlayer-ui`.
8
+
9
+ ## Package Migration Notice
10
+
11
+ Previous versions listed after this indicator refer to our deprecated `@pxblue` packages.
12
+
13
+ ---
14
+
15
+ ## v2.0.0 (November 3, 2021)
16
+
17
+ ### Changed
18
+
19
+ - Updated to use latest APIs from `@pxblue/react-native-components`.
20
+
21
+ ## v1.3.2 (October 1, 2021)
22
+
23
+ ### Fixed
24
+
25
+ - Added dependency for `@pxblue/react-native-vector-icons`
26
+
27
+ ## v1.3.1 (October 1, 2021)
28
+
29
+ ### Fixed
30
+
31
+ - Issue with template using the wrong dependency for `@pxblue/react-native-auth-workflow`
32
+
33
+ ## v1.3.0 (September 30, 2021)
34
+
35
+ ### Changed
36
+ - Upgrade PX Blue packages
37
+ - Changed default drawer width to 300.
38
+
39
+ ## v1.2.0 (July 26, 2021)
40
+
41
+ ### Changed
42
+ - Updated version of react-native-modal dependency to 12.0.2
43
+
44
+ ## v1.1.1 (June 30, 2021)
45
+
46
+ ### Changed
47
+ - Dropped version of react-native-webview peerDependency to match requirements of @pxblue/react-native-auth-workflow.
48
+
49
+ ## v1.1.0 (March 31, 2021)
50
+
51
+ ### Changed
52
+ - Updated dependencies to use latest packages.
53
+ - Reorganized folder structure
54
+
55
+ ### Fixed
56
+ - Jest configuration allows default tests to pass.
57
+
58
+
59
+ ## v1.0.0 (March 1, 2021)
60
+
61
+ ### Added
62
+
63
+ - Initial authentication template for TypeScript projects
package/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # Authentication Template (TypeScript)
2
+
3
+ ![npm (scoped)](https://img.shields.io/npm/v/@brightlayer-ui/react-native-template-authentication-typescript?color=%23007bc1&label=%40brightlayer-ui%2Freact-native-template-authentication-typescript)
4
+
5
+ This is an official Brightlayer UI template used internally by the [Brightlayer UI CLI](https://www.npmjs.com/package/@brightlayer-ui/cli).
6
+
7
+ This template installs and configures the Brightlayer UI [react-native-auth-workflow](https://www.npmjs.com/package/@brightlayer-ui/react-native-auth-workflow) package to automatically wrap your main application with functions and screens for Login, Registration, Change Password, etc. This uses the same configurations as the sample project for that package — you will need to replace the API integrations with implementations specific to your API. This template also includes the installation and initial setup of routing using [React Navigation](https://reactnavigation.org/). It includes several placeholder routes/screens and a [Drawer](https://brightlayer-ui-components.github.io/react-native/?path=/info/components-documentation--drawer) navigator from the Brightlayer UI [React Native Component Library](https://www.npmjs.com/package/@brightlayer-ui/react-native-components).
8
+
9
+ ## Usage
10
+ This template can be used with the Brightlayer UI CLI:
11
+ ```sh
12
+ npx -p @brightlayer-ui/cli blui new react-native --name=myapp --cli=rnc --language=ts --template=authentication
13
+ ```
14
+
15
+ ## Project Structure
16
+ Projects created using this template will start out with the following file structure:
17
+
18
+ ```
19
+ |── /ios // ios project folder
20
+ |── /android // android project folder
21
+ |── /actions
22
+ | |── AuthUIActions.tsx // handles the implementation of the authentication related actions (such as login and forgot password)
23
+ | └── RegistrationUIActions.tsx // handles the implementation of the registration related actions (such as loading the EULA and registration by invitation)
24
+ |── /assets // fonts and images used by the application
25
+ |── App.tsx // app entry point
26
+ |── /constants
27
+ | |── index.ts // application constants
28
+ | └── sampleEula.ts // sample Eula
29
+ |── /pages // sample application pages
30
+ |── /router
31
+ | |── index.tsx // sets up routing
32
+ | |── DeepLinking.ts // sets up deep linking
33
+ | └── navigation-drawer.tsx // sets up Drawer
34
+ └── /store
35
+ └── local-storage.ts // mock implementation for storing/retrieving user authentication session data
36
+ ```
@@ -0,0 +1,31 @@
1
+ {
2
+ "dependencies": [
3
+ "@brightlayer-ui/colors@^3.0.1",
4
+ "@brightlayer-ui/icons-svg@^1.8.0",
5
+ "@brightlayer-ui/react-native-auth-workflow@^4.0.0",
6
+ "@brightlayer-ui/react-native-components@^6.0.1",
7
+ "@brightlayer-ui/react-native-themes@^6.0.0",
8
+ "@brightlayer-ui/react-native-vector-icons@^1.4.0",
9
+ "@react-native-async-storage/async-storage@^1.14.1",
10
+ "@react-native-community/masked-view@^0.1.10",
11
+ "@react-navigation/drawer@^5.12.3",
12
+ "@react-navigation/native@^5.9.3",
13
+ "@react-navigation/stack@^5.14.3",
14
+ "date-fns@^2.19.0",
15
+ "i18next@^20.0.0",
16
+ "react-i18next@^11.8.7",
17
+ "react-native-gesture-handler@^1.10.3",
18
+ "react-native-keyboard-aware-scroll-view@^0.9.3",
19
+ "react-native-modal@^12.0.2",
20
+ "react-native-pager-view@^5.0.0",
21
+ "react-native-paper@^4.7.2",
22
+ "react-native-reanimated@^1.0.0",
23
+ "react-native-safe-area-context@^3.2.0",
24
+ "react-native-screens@^3.0.0",
25
+ "react-native-svg@^12.1.0",
26
+ "react-native-svg-transformer@^0.14.3",
27
+ "react-native-vector-icons@^8.1.0",
28
+ "react-native-webview@^10.3.2"
29
+ ],
30
+ "devDependencies": ["@types/react-native-vector-icons", "jest-transform-stub"]
31
+ }
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,11 @@
1
+
2
+ <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 82 82" width='82' height='82'>
3
+
4
+ <ellipse cx="41" cy="41" rx="11.2" ry="11.1"/>
5
+ <path d="M72.7,33.9c2.4,10.9-0.8,22.1-8.7,30C51.4,76.6,30.8,76.6,18.2,64S5.4,30.8,18,18.1c5-5,11.4-8.2,18.4-9.2
6
+ c0.3,0.9,0.8,1.8,1.5,2.5c2.3,2.3,6.1,2.3,8.4,0c0.9-0.9,1.5-2.1,1.7-3.5l0,0c0.2-1.8-0.4-3.6-1.7-4.9c-2.3-2.3-6.1-2.3-8.4,0
7
+ c-0.9,0.9-1.5,2.1-1.7,3.3c-7.6,1-14.6,4.6-20,10C2.6,30,2.7,52.2,16.3,65.8s35.9,13.6,49.5-0.1c8.4-8.5,11.9-20.5,9.4-32.2l0,0
8
+ c-1.4-6.4-4.8-12.1-9.5-16.8l0,0C65.7,16.7,71.2,23.3,72.7,33.9z M37.4,7.5L37.4,7.5L37.4,7.5L37.4,7.5z M45.5,7.6
9
+ c-0.1,0.8-0.4,1.5-1,2.1c-1.4,1.4-3.6,1.4-4.9,0c-0.6-0.6-1-1.4-1-2.3l0,0c-0.1-1,0.3-2,1-2.7c1.4-1.4,3.6-1.4,4.9,0
10
+ C45.3,5.5,45.7,6.6,45.5,7.6z"/>
11
+ </svg>
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@brightlayer-ui/react-native-template-authentication-typescript",
3
+ "version": "2.0.0-beta.0",
4
+ "author": "brightlayer-ui <brightlayer-ui@eaton.com>",
5
+ "keywords": [
6
+ "react native",
7
+ "template",
8
+ "brightlayer-ui",
9
+ "cli"
10
+ ],
11
+ "description": "A template with pre-configured login workflow and routing for React Native projects created with the Brightlayer UI CLI.",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/brightlayer-ui/react-native-cli-templates.git"
15
+ },
16
+ "scripts": {
17
+ "publish:package": "set npm_config_yes=true && npx -p @brightlayer-ui/publish blui-publish",
18
+ "tag:package": "set npm_config_yes=true && npx -p @brightlayer-ui/tag blui-tag"
19
+ },
20
+ "license": "BSD-3-Clause",
21
+ "engines": {
22
+ "node": ">=10"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/brightlayer-ui/react-native-cli-templates/issues"
26
+ }
27
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ Copyright (c) 2021-present, Eaton
3
+
4
+ All rights reserved.
5
+
6
+ This code is licensed under the BSD-3 license found in the LICENSE file in the root directory of this source tree and at https://opensource.org/licenses/BSD-3-Clause.
7
+ **/
8
+ import React from 'react';
9
+ import { Provider as ThemeProvider } from 'react-native-paper';
10
+ import { SafeAreaProvider } from 'react-native-safe-area-context';
11
+ import * as BLUIThemes from '@brightlayer-ui/react-native-themes';
12
+ import { MainRouter } from './src/navigation';
13
+ import { ProjectAuthUIActions } from './src/actions/AuthUIActions';
14
+ import { ProjectRegistrationUIActions } from './src/actions/RegistrationUIActions';
15
+ import {
16
+ SecurityContextProvider,
17
+ AuthNavigationContainer,
18
+ AuthUIContextProvider,
19
+ useSecurityActions,
20
+ } from '@brightlayer-ui/react-native-auth-workflow';
21
+ import { useLinking } from '@react-navigation/native';
22
+ import { authLinkMapping, resolveInitialState } from './src/navigation/DeepLinking';
23
+
24
+ export const AuthUIConfiguration: React.FC = (props) => {
25
+ const securityContextActions = useSecurityActions();
26
+ return (
27
+ <AuthUIContextProvider
28
+ authActions={ProjectAuthUIActions(securityContextActions)}
29
+ registrationActions={ProjectRegistrationUIActions}
30
+ showSelfRegistration={true}
31
+ allowDebugMode={true}
32
+ contactEmail={'something@email.com'}
33
+ contactPhone={'1-800-123-4567'}
34
+ contactPhoneLink={'1-800-123-4567'}
35
+ // projectImage={require('./src/assets/images/some_image.png')}
36
+ >
37
+ {props.children}
38
+ </AuthUIContextProvider>
39
+ );
40
+ };
41
+
42
+ export const App = (): JSX.Element => {
43
+ const ref = React.useRef(null);
44
+ const { getInitialState } = useLinking(ref, authLinkMapping);
45
+ const [initialState, setInitialState] = React.useState();
46
+ React.useEffect(() => {
47
+ resolveInitialState(getInitialState, setInitialState);
48
+ }, [getInitialState]);
49
+
50
+ return (
51
+ <ThemeProvider theme={BLUIThemes.blue}>
52
+ <SafeAreaProvider>
53
+ <SecurityContextProvider>
54
+ <AuthUIConfiguration>
55
+ <AuthNavigationContainer initialState={initialState} ref={ref}>
56
+ <MainRouter />
57
+ </AuthNavigationContainer>
58
+ </AuthUIConfiguration>
59
+ </SecurityContextProvider>
60
+ </SafeAreaProvider>
61
+ </ThemeProvider>
62
+ );
63
+ };
64
+
65
+ export default App;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * SVG setup to mock react components transformed into images from SVGs
3
+ * Place this file in your project's root __mocks__ directory.
4
+ * https://github.com/kristerkari/react-native-svg-transformer#usage-with-jest
5
+ */
6
+
7
+ module.exports = 'SvgMock';
8
+ module.exports.ReactComponent = 'SvgMock';
@@ -0,0 +1,17 @@
1
+ import 'react-native-gesture-handler/jestSetup';
2
+ import mockAsyncStorage from '@react-native-async-storage/async-storage/jest/async-storage-mock';
3
+
4
+ jest.useFakeTimers();
5
+ jest.mock('@react-native-async-storage/async-storage', () => mockAsyncStorage);
6
+ jest.mock('react-native-reanimated', () => {
7
+ const Reanimated = require('react-native-reanimated/mock');
8
+
9
+ // The mock for `call` immediately calls the callback which is incorrect
10
+ // So we override it with a no-op
11
+ Reanimated.default.call = () => {};
12
+
13
+ return Reanimated;
14
+ });
15
+
16
+ // Silence the warning: Animated: `useNativeDriver` is not supported because the native animated module is missing
17
+ jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper');
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Metro configuration for React Native
3
+ * https://github.com/facebook/react-native
4
+ *
5
+ * @format
6
+ */
7
+
8
+ const { getDefaultConfig } = require('metro-config');
9
+
10
+ module.exports = (async () => {
11
+ const {
12
+ resolver: { sourceExts, assetExts },
13
+ } = await getDefaultConfig();
14
+ return {
15
+ transformer: {
16
+ babelTransformerPath: require.resolve('react-native-svg-transformer'),
17
+ getTransformOptions: async () => ({
18
+ transform: {
19
+ experimentalImportSupport: false,
20
+ inlineRequires: true,
21
+ },
22
+ }),
23
+ },
24
+ resolver: {
25
+ assetExts: assetExts.filter((ext) => ext !== 'svg'),
26
+ sourceExts: [...sourceExts, 'svg'],
27
+ },
28
+ };
29
+ })();
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ assets: ['./assets/fonts/'],
3
+ };
@@ -0,0 +1,178 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ import { AuthUIActions, SecurityContextActions } from '@brightlayer-ui/react-native-auth-workflow';
3
+ import { LocalStorage } from '../store/local-storage';
4
+
5
+ const sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));
6
+
7
+ function getRandomInt(max: number): number {
8
+ return Math.floor(Math.random() * Math.floor(max));
9
+ }
10
+
11
+ function isRandomFailure(): boolean {
12
+ const randomResponseNumber = getRandomInt(100);
13
+ return false; // randomResponseNumber < 10;
14
+ }
15
+
16
+ type AuthUIActionsFunction = () => AuthUIActions;
17
+ type AuthUIActionsWithSecurity = (securityHelper: SecurityContextActions) => AuthUIActionsFunction;
18
+
19
+ /**
20
+ * Example implementation of [[AuthUIActions]] to start with during development.
21
+ *
22
+ * Authentication Actions to be performed based on the user's UI actions. The application will create
23
+ * appropriate actions (often api calls, local network storage, credential updates, etc) and update
24
+ * the global security state based on the actionable needs of the user.
25
+ */
26
+ export const ProjectAuthUIActions: AuthUIActionsWithSecurity = (securityHelper) => (): AuthUIActions => ({
27
+ /**
28
+ * Initialize the application security state. This will involve reading any local storage,
29
+ * validating existing credentials (token expiration, for example). At the end of validation,
30
+ * the [[SecurityContextActions]] should be called with either:
31
+ * [[onUserAuthenticated]] (which will present the application), or
32
+ * [[onUserNotAuthenticated]] (which will present the Auth UI).
33
+ *
34
+ * Note: Until this method returns, the applications Splash screen will be presented.
35
+ *
36
+ * @returns Should always resolve. Never throw.
37
+ */
38
+ initiateSecurity: async (): Promise<void> => {
39
+ let authData;
40
+
41
+ try {
42
+ await sleep(2000);
43
+ authData = await LocalStorage.readAuthData();
44
+ } catch (e) {
45
+ // Restoring token failed
46
+ }
47
+
48
+ // After restoring token, we may need to validate it in production apps
49
+ // This will switch to the App screen or Auth screen and this loading
50
+ // screen will be unmounted and thrown away.
51
+ // securityHelper.onUserAuthenticated()
52
+ if (authData?.email !== undefined) {
53
+ securityHelper.onUserAuthenticated({
54
+ email: authData?.email,
55
+ userId: authData.userId ?? '',
56
+ rememberMe: authData?.rememberMeData.rememberMe,
57
+ });
58
+ } else {
59
+ const rememberMeEmail = authData?.rememberMeData.rememberMe ? authData?.rememberMeData.user : undefined;
60
+ securityHelper.onUserNotAuthenticated(false, rememberMeEmail);
61
+ }
62
+ },
63
+ /**
64
+ * The user wants to log into the application. Perform a login with the user's credentials.
65
+ * The application should provide the user's email and password to the authentication server.
66
+ *
67
+ * In the case of valid credentials, the applications code should store the returned data
68
+ * (such as token, user information, etc.). Then the [[onUserAuthenticated]] function should
69
+ * be called on the [[SecurityContextActions]] object.
70
+ *
71
+ * For example:
72
+ * ```
73
+ * LocalStorage.saveAuthCredentials(email, email);
74
+ * LocalStorage.saveRememberMeData(email, rememberMe);
75
+ *
76
+ * securityHelper.onUserAuthenticated({ email: email, userId: email, rememberMe: rememberMe });
77
+ * ```
78
+ *
79
+ * In the case of invalid credentials, an error should be thrown.
80
+ *
81
+ * @param email Email address the user entered into the UI.
82
+ * @param password Password the user entered into the UI.
83
+ * @param rememberMe Indicates whether the user's email should be remembered on success.
84
+ *
85
+ * @returns Resolve if code is credentials are valid, otherwise reject.
86
+ */
87
+ logIn: async (email: string, password: string, rememberMe: boolean): Promise<void> => {
88
+ await sleep(1000);
89
+
90
+ if (isRandomFailure()) {
91
+ // reject(new Error('LOGIN.GENERIC_ERROR'));
92
+ throw new Error('LOGIN.INVALID_CREDENTIALS');
93
+ }
94
+
95
+ LocalStorage.saveAuthCredentials(email, email);
96
+ LocalStorage.saveRememberMeData(email, rememberMe);
97
+
98
+ securityHelper.onUserAuthenticated({ email: email, userId: email, rememberMe: rememberMe });
99
+ },
100
+ /**
101
+ * The user has forgotten their password and wants help.
102
+ * The application generally should call an API which will then send a password reset
103
+ * link to the user's email.
104
+ *
105
+ * @param email Email address the user entered into the UI.
106
+ *
107
+ * @returns Resolve if email sending was successful, otherwise reject.
108
+ */
109
+ forgotPassword: async (email: string): Promise<void> => {
110
+ await sleep(500);
111
+ if (isRandomFailure()) {
112
+ throw new Error('Sorry, there was a problem sending your request.');
113
+ }
114
+
115
+ return;
116
+ },
117
+ /**
118
+ * The user has tapped on an email with a password reset link, which they received after
119
+ * requesting help for forgetting their password.
120
+ * The application should take the password reset code and then verify that it is still
121
+ * valid.
122
+ *
123
+ * @param code Password reset code from a reset password link.
124
+ * @param email Email if it was passed from the reset link
125
+ *
126
+ * @returns Resolve if code is valid, otherwise reject.
127
+ */
128
+ verifyResetCode: async (code: string, email?: string): Promise<void> => {
129
+ await sleep(500);
130
+ if (isRandomFailure()) {
131
+ throw new Error('Sorry, there was a problem sending your request.');
132
+ }
133
+
134
+ return;
135
+ },
136
+ /**
137
+ * A user who has previously used "forgotPassword" now has a valid password reset code
138
+ * and has entered a new password.
139
+ * The application should take the user's password reset code and the newly entered
140
+ * password and then reset the user's password.
141
+ *
142
+ * Note: Upon success, the user will be taken to the Login screen.
143
+ *
144
+ * @param code Password reset code from a link
145
+ * @param password New Password the user entered into the UI
146
+ * @param email Email if it was passed from the reset link
147
+ *
148
+ * @returns Resolve if successful, otherwise reject with an error message.
149
+ */
150
+ setPassword: async (code: string, password: string, email?: string): Promise<void> => {
151
+ await sleep(500);
152
+ if (isRandomFailure()) {
153
+ throw new Error('Sorry, there was a problem sending your request.');
154
+ }
155
+
156
+ return;
157
+ },
158
+ /**
159
+ * An authenticated user wants to change their password.
160
+ * The application should try to change the user's password. Upon completion,
161
+ * the user will be logged out of the application. Upon cancellation, the user
162
+ * will be taken back to the application's home screen.
163
+ *
164
+ * @param oldPassword The user's current password as entered into the UI.
165
+ * @param newPassword The user's new password as entered into the UI.
166
+ *
167
+ * @returns Resolve if successful, otherwise reject with an error message.
168
+ */
169
+ changePassword: async (oldPassword: string, newPassword: string): Promise<void> => {
170
+ await sleep(1000);
171
+
172
+ if (isRandomFailure()) {
173
+ throw new Error('Sorry, there was a problem sending your request.');
174
+ }
175
+
176
+ return;
177
+ },
178
+ });
@@ -0,0 +1,111 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ import { RegistrationUIActions, AccountDetailInformation } from '@brightlayer-ui/react-native-auth-workflow';
3
+
4
+ // Constants
5
+ import { SAMPLE_EULA } from '../constants/sampleEula';
6
+
7
+ const sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));
8
+
9
+ function getRandomInt(max: number): number {
10
+ return Math.floor(Math.random() * Math.floor(max));
11
+ }
12
+
13
+ function isRandomFailure(): boolean {
14
+ const randomResponseNumber = getRandomInt(100);
15
+ return false; // randomResponseNumber < 90;
16
+ }
17
+
18
+ /**
19
+ * Example implementation of [[RegistrationUIActions]] to start with during development.
20
+ *
21
+ * Registration Actions to be performed based on the user's actions. The application will create appropriate actions
22
+ * (often API calls, local network storage, credential updates, etc.) based on the actionable needs of the user.
23
+ */
24
+ export const ProjectRegistrationUIActions: () => RegistrationUIActions = () => ({
25
+ /**
26
+ * The user wants to complete an action but must first accept the EULA.
27
+ * The application should retrieve an application-specific EULA for the user.
28
+ *
29
+ * @param language The i18n language the user is requesting for the EULA text.
30
+ *
31
+ * @returns Resolve with EULA, otherwise reject with an error message.
32
+ */
33
+ loadEULA: async (language: string): Promise<string> => {
34
+ await sleep(1000);
35
+
36
+ if (isRandomFailure()) {
37
+ throw new Error('Sorry, there was a problem sending your request.');
38
+ }
39
+
40
+ if (language !== 'en' && language !== 'en_US') {
41
+ return 'Other language EULA';
42
+ }
43
+
44
+ return SAMPLE_EULA;
45
+ },
46
+
47
+ /**
48
+ * The user entered their email address and accepted the EULA.
49
+ * The API should now send them an email with the validation code.
50
+ *
51
+ * @param email Email for the registering user.
52
+ *
53
+ * @returns Resolve when the server accepted the request.
54
+ */
55
+ requestRegistrationCode: async (email: string): Promise<void> => {
56
+ await sleep(800);
57
+ if (isRandomFailure()) {
58
+ throw new Error('Sorry, there was a problem sending your request.');
59
+ }
60
+ },
61
+
62
+ /**
63
+ * The user has tapped on an email link inviting them to register with the application.
64
+ * The application should validate the code provided by the link.
65
+ *
66
+ * @param validationCode Registration code provided from the link.
67
+ * @param validationEmail Email provided from the invitation email link (optional) `?email=addr%40domain.com`.
68
+ *
69
+ * @returns Resolves when the code is valid. True if registration is complete, False if account information is needed.
70
+ * If the code is not valid a rejection will occur with an error message.
71
+ */
72
+ validateUserRegistrationRequest: async (validationCode: string, validationEmail?: string): Promise<boolean> => {
73
+ await sleep(800);
74
+
75
+ if (isRandomFailure()) {
76
+ throw new Error('Sorry, there was a problem sending your request.');
77
+ }
78
+ return isRandomFailure();
79
+ },
80
+ /**
81
+ * The user has been invited to register and has entered the necessary account and
82
+ * password information.
83
+ * The application should now complete the registration process given the user's data.
84
+ *
85
+ * Note: Upon resolution, the user will be brought back to the Login screen.
86
+ *
87
+ * @param userData Account details and password entered by the user.
88
+ * @param validationCode Registration code provided from the invitation email link.
89
+ * @param validationEmail Email provided from the invitation email link (optional) `?email=addr%40domain.com`.
90
+ *
91
+ * @returns Resolve when account creation succeeds, otherwise reject with an error message.
92
+ */
93
+ completeRegistration: async (
94
+ userData: {
95
+ password: string;
96
+ accountDetails: AccountDetailInformation;
97
+ },
98
+ validationCode: string,
99
+ validationEmail?: string
100
+ ): Promise<{ email: string; organizationName: string }> => {
101
+ const email = 'example@email.com';
102
+ const organizationName = 'Acme Co.';
103
+ const userInfo = { email, organizationName };
104
+
105
+ await sleep(1000);
106
+ if (isRandomFailure()) {
107
+ throw new Error('Sorry, there was a problem sending your request.');
108
+ }
109
+ return userInfo;
110
+ },
111
+ });
@@ -0,0 +1,3 @@
1
+ export const USER_SETTINGS = 'user_settings';
2
+ export const LOCAL_USER_DATA = 'user_data';
3
+ export const REMEMBER_ME_DATA = 'remember_me_data';
@@ -0,0 +1 @@
1
+ export const SAMPLE_EULA = `THIS EULA IS ONLY BETWEEN EATON CORPORATION (“EATON”) AND THE END USER TO WHICH THE EATON WEB APPLICATION (THE “APP”) IS PROVIDED (“YOU”). BY ACCESSING, OR USING THE APP, YOU AGREE TO BE BOUND BY THIS EULA. IF YOU DO NOT AGREE WITH THE EULA, THEN DO NOT USE THIS APP.\n\nSubject to your acceptance of this EULA, Eaton grants you, a limited, nonexclusive, nontransferable, revocable license to access and use the App via the Internet for your personal and noncommercial purposes and as permitted by the Terms of Use. Eaton is the licensor of the App. You may not modify, reverse engineer, decompile, or disassemble the App, in whole or in part, or create any derivative works from or sublicense any rights in the App, unless otherwise expressly authorized in writing by Eaton. You may not translate or otherwise attempt to create the source code from the App, or rent, lease, grant a security interest in, or otherwise transfer any rights to, copy, distribute, transmit, display, perform, reproduce, publish, license, or transfer the App, or remove or alter any trademark, logo, ©, patent or other proprietary notices in the App. The App is protected by © and other intellectual property laws and treaties. Unless expressly stated in this EULA, Eaton owns all title, © and other intellectual property and proprietary rights in and to the App, and the App is licensed and not sold. You acknowledge and agree that Eaton will have no liability to you for any intellectual property infringement or violation, or for violation of this EULA or any license, arising out of or resulting from the use of the App in combination with any other product or service not approved or supplied by Eaton, or modification of the App by you or for you. If you breach this EULA, Eaton may immediately terminate this EULA, whereupon you shall immediately cease using the App. By accepting this EULA, you will have the right pursuant to this EULA to access and use the App. By using this App you will be required to provide Eaton certain personal data in accordance with the Eaton Privacy Policy (“Privacy Policy”). This data includes, but is not limited to: (a) first and last name, (b) phone number, (c) email address, (d) shopping and/or product preferences and history, (e) purchase history, and (f) device data including, but not limited to, Internet Protocol address, location data, Operating System version, and language (“Personal Data”). If you register for the App through a social profile such as Facebook or Google+, tallyo may also process and store your social network profile, which may include, but is not limited to, additional Personal Data that you choose to make accessible to Eaton such as (x) friend or contact names, (y) date of birth, and (z) likes and dislikes. You hereby agree to allow Eaton and the App to access, store and use any information directly related to the App, including, without limitation, Personal Data. Eaton agrees to comply with all applicable privacy laws in connection with its collection, storage and use of Personal Data. The Privacy Policy is incorporated herein by reference All rights not expressly granted to you are retained by Eaton. You must comply with applicable third party terms of agreement when using the App. In the event of a breach of this EULA by Eaton, Eaton's entire liability and your exclusive remedy shall be repair or replacement of the App, except that such remedy will not be available if any problem with the App arises from your violation of this EULA, accident, abuse, misapplication, abnormal or unauthorized access or use, or introduction of a virus or malicious code by a person other than Eaton. Eaton or its suppliers are solely responsible for providing maintenance and support services with respect to the App (Support Services). Support Services are governed by the policies and programs described in the Eaton-provided materials or as required under applicable law. Any software code provided to you as part of the Support Services is part of the App and subject to this EULA. You agree that access to and use of the App will not be provided by you or any employee or agent of you to any citizen of a country to which access or use thereof is barred, or to which exports or shipments are barred, by the US government, and that you will not ship, transfer or export the App into any country or use it in any manner prohibited by the United States Export Administration Act or any other export laws, restrictions or regulations (Export Laws). You represent and warrant that you are not located in or a citizen of a country that is subject to a US government embargo, that you are not listed on any US government list of prohibited or restricted parties, and that you are not otherwise prohibited under the Export Laws from receiving access to or using the App. ACCESS TO AND USE OF THE APP IS PROVIDED “AS IS.” ALTHOUGH THE APP IS BELIEVED TO BE ACCURATE, IT IS FOR INFORMATIONAL PURPOSES ONLY. YOU ASSUME TOTAL RESPONSIBILITY AND RISK FOR USE OF THE APP. EATON DOES NOT GUARANTEE CONTINUOUS, UNINTERRUPTED OR SECURE ACCESS TO OR USE OF THE APP. NO WARRANTY OR CONDITION, EXPRESS OR IMPLIED, IS MADE WITH RESPECT TO THE APP, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE PARTIES AGREE THAT EATON IS NOT PROVIDING ANY WARRANTY OR OTHER ASSURANCE THAT THE APP IS FREE OF THE RIGHTFUL CLAIM OF ANY THIRD PARTY BY WAY OF INFRINGEMENT OR OTHERWISE. EATON SHALL NOT BE LIABLE TO YOU FOR SPECIAL, INCIDENTAL, CONSEQUENTIAL, EXEMPLARY OR OTHER DAMAGES RESULTING FROM ACCESS TO OR USE OF THE APP, HOWEVER CAUSED. IN NO EVENT SHALL EATON’S TOTAL LIABILITY ARISING IN CONNECTION WITH OR UNDER THIS EULA EXCEED THE TOTAL FEES ACTUALLY PAID TO EATON BY YOU UNDER THIS EULA. This EULA will be construed in accordance with the substantive laws of the State of Ohio, without regard to its principles of conflicts of law. This EULA contains the entire understanding between you and Eaton regarding its subject matter and supersedes all prior agreements between you and Eaton (oral or written) regarding such subject matter. This EULA may be modified from time to time by Eaton. Any waiver of any provision must be in writing. If any provision of this EULA is found illegal or unenforceable, the remaining provisions will remain in full force and effect. `;
@@ -0,0 +1,62 @@
1
+ import { LinkingOptions } from '@react-navigation/native/lib/typescript/src/types';
2
+
3
+ /**
4
+ * Map a Deep Link or Universal link to a screen in the application
5
+ * https://reactnavigation.org/docs/deep-linking
6
+ */
7
+
8
+ export const authLinkMapping: LinkingOptions = {
9
+ prefixes: ['https://authui.com', 'authui://'],
10
+ config: {
11
+ screens: {
12
+ Login: 'login',
13
+ PasswordResetInitiation: {
14
+ initialRouteName: 'Login',
15
+ path: 'password/reset/initiate',
16
+ },
17
+ PasswordResetCompletion: {
18
+ initialRouteName: 'Login',
19
+ // email can be passed in as parameter if needed for the api
20
+ path: 'password/reset/:code',
21
+ },
22
+ RegistrationInvite: {
23
+ initialRouteName: 'Login',
24
+ // email can be passed in as parameter if needed for the api
25
+ path: 'invite/:code',
26
+ },
27
+ Registration: {
28
+ initialRouteName: 'Login',
29
+ // email can be passed in as parameter if needed for the api
30
+ path: 'register/:code',
31
+ },
32
+ SupportContact: {
33
+ initialRouteName: 'Login',
34
+ path: 'support',
35
+ },
36
+ },
37
+ },
38
+ };
39
+
40
+ /**
41
+ * React Native boilerplate to get the initial state from open link, or fail after 150ms
42
+ * @param getInitialState
43
+ * @param setInitialState
44
+ */
45
+ export const resolveInitialState = (getInitialState: () => any, setInitialState: (state: any) => void): void => {
46
+ Promise.race([
47
+ getInitialState(),
48
+ new Promise((resolve) =>
49
+ // Timeout in 150ms if `getInitialState` doesn't resolve
50
+ // Workaround for https://github.com/facebook/react-native/issues/25675
51
+ setTimeout(resolve, 150)
52
+ ),
53
+ ])
54
+ .then((state) => {
55
+ if (state !== undefined) {
56
+ setInitialState(state);
57
+ }
58
+ })
59
+ .catch((e) => {
60
+ console.error(e);
61
+ });
62
+ };
@@ -0,0 +1,37 @@
1
+ import React, { ReactNode } from 'react';
2
+ import { createDrawerNavigator } from '@react-navigation/drawer';
3
+ import { View } from 'react-native';
4
+ import { NavDrawerProps, NavigationDrawer } from './navigation-drawer';
5
+ import { createStackNavigator } from '@react-navigation/stack';
6
+ import Home from '../screens/home';
7
+ import PageOne from '../screens/pageOne';
8
+ import PageTwo from '../screens/pageTwo';
9
+
10
+ const Drawer = createDrawerNavigator();
11
+
12
+ export type RootStackParamList = {
13
+ Home: undefined;
14
+ PageOne: undefined;
15
+ PageTwo: undefined;
16
+ NavigationDrawer: undefined;
17
+ };
18
+
19
+ const RootStack = createStackNavigator<RootStackParamList>();
20
+
21
+ const CustomDrawerContent = (props: any): any => (
22
+ <View style={{ height: '100%' }}>
23
+ <NavigationDrawer {...props} />
24
+ </View>
25
+ );
26
+
27
+ export const MainRouter = (): any => (
28
+ <Drawer.Navigator
29
+ initialRouteName="Home"
30
+ drawerStyle={{ backgroundColor: 'transparent', width: 300, maxWidth: '80%' }}
31
+ drawerContent={(props: NavDrawerProps): ReactNode => <CustomDrawerContent {...props} />}
32
+ >
33
+ <RootStack.Screen name="Home" component={Home} />
34
+ <RootStack.Screen name="PageOne" component={PageOne} />
35
+ <RootStack.Screen name="PageTwo" component={PageTwo} />
36
+ </Drawer.Navigator>
37
+ );
@@ -0,0 +1,55 @@
1
+ import { Drawer, DrawerBody, DrawerHeader, DrawerNavGroup, NavItem } from '@brightlayer-ui/react-native-components';
2
+ import React, { useState, useCallback } from 'react';
3
+ import * as Colors from '@brightlayer-ui/colors';
4
+ import { StackNavigationProp } from '@react-navigation/stack';
5
+ import { RootStackParamList } from './index';
6
+
7
+ export const navGroupItems: NavItem[] = [
8
+ {
9
+ title: 'Home Page',
10
+ itemID: 'Home',
11
+ icon: { name: 'home' },
12
+ },
13
+ {
14
+ title: 'Page One',
15
+ itemID: 'PageOne',
16
+ icon: { name: 'looks-one' },
17
+ },
18
+ {
19
+ title: 'Page Two',
20
+ itemID: 'PageTwo',
21
+ icon: { name: 'looks-two' },
22
+ },
23
+ ];
24
+
25
+ export type NavDrawerProps = {
26
+ navigation: StackNavigationProp<RootStackParamList, 'NavigationDrawer'>;
27
+ };
28
+
29
+ export const NavigationDrawer: React.FC<NavDrawerProps> = ({ navigation }) => {
30
+ const [selected, setSelected] = useState('Home');
31
+ const selectItem = useCallback(
32
+ (id) => {
33
+ navigation.navigate(id);
34
+ setSelected(id);
35
+ },
36
+ [navigation]
37
+ );
38
+
39
+ return (
40
+ <Drawer activeItem={selected} onItemSelect={(id: string): void => selectItem(id)}>
41
+ <DrawerHeader
42
+ title={'Brightlayer UI'}
43
+ subtitle={'React Native Project'}
44
+ fontColor={Colors.white[50]}
45
+ icon={{ name: 'menu' }}
46
+ onIconPress={(): void => {
47
+ navigation.closeDrawer();
48
+ }}
49
+ />
50
+ <DrawerBody>
51
+ <DrawerNavGroup items={navGroupItems} hidePadding={false} />
52
+ </DrawerBody>
53
+ </Drawer>
54
+ );
55
+ };
@@ -0,0 +1,198 @@
1
+ import React, { useCallback } from 'react';
2
+ import {
3
+ SafeAreaView,
4
+ ScrollView,
5
+ StyleSheet,
6
+ Linking,
7
+ TextStyle,
8
+ ViewStyle,
9
+ View,
10
+ Animated,
11
+ Easing,
12
+ } from 'react-native';
13
+ import { Avatar, Button, Divider, useTheme } from 'react-native-paper';
14
+ import { Body1, H4, Header, IconFamily, InfoListItemProps, UserMenu } from '@brightlayer-ui/react-native-components';
15
+ import { Theme } from 'react-native-paper/lib/typescript/types';
16
+ import Logo from '../../assets/images/Logo.svg';
17
+ import { StackNavigationProp } from '@react-navigation/stack';
18
+ import { RootStackParamList } from '../navigation';
19
+ import { useSecurityActions } from '@brightlayer-ui/react-native-auth-workflow';
20
+ import { LocalStorage } from '../store/local-storage';
21
+ import * as Colors from '@brightlayer-ui/colors';
22
+
23
+ const MenuIcon: IconFamily = { name: 'menu', direction: 'ltr' };
24
+ const LockIcon: IconFamily = { name: 'lock', direction: 'ltr' };
25
+ const ExitToAppIcon: IconFamily = { name: 'exit-to-app', direction: 'ltr' };
26
+
27
+ const styles = (
28
+ theme: Theme
29
+ ): StyleSheet.NamedStyles<{
30
+ content: ViewStyle;
31
+ pxbLogoWrapper: ViewStyle;
32
+ pxbLogo: ViewStyle;
33
+ title: TextStyle;
34
+ subtitle: TextStyle;
35
+ bold: TextStyle;
36
+ divider: ViewStyle;
37
+ openURLButtonText: TextStyle;
38
+ }> =>
39
+ StyleSheet.create({
40
+ content: {
41
+ flex: 1,
42
+ },
43
+ pxbLogoWrapper: {
44
+ justifyContent: 'center',
45
+ marginTop: 16,
46
+ },
47
+ pxbLogo: {
48
+ alignSelf: 'center',
49
+ height: 100,
50
+ width: 100,
51
+ },
52
+ title: {
53
+ textAlign: 'center',
54
+ marginBottom: 16,
55
+ },
56
+ subtitle: {
57
+ textAlign: 'center',
58
+ },
59
+ bold: {
60
+ fontWeight: 'bold',
61
+ },
62
+ divider: {
63
+ marginVertical: 24,
64
+ },
65
+ openURLButtonText: {
66
+ color: theme.colors.text,
67
+ padding: 8,
68
+ },
69
+ });
70
+
71
+ const OpenURLButton = (props: any): JSX.Element => {
72
+ const { url, title } = props;
73
+ const theme = useTheme();
74
+ const defaultStyles = styles(theme);
75
+
76
+ const handlePress = useCallback(async () => {
77
+ await Linking.openURL(url);
78
+ }, [url]);
79
+
80
+ return (
81
+ <Button
82
+ onPress={(): Promise<void> => handlePress()}
83
+ labelStyle={defaultStyles.openURLButtonText}
84
+ uppercase={false}
85
+ >
86
+ {title}
87
+ </Button>
88
+ );
89
+ };
90
+
91
+ type AppProps = {
92
+ navigation: StackNavigationProp<RootStackParamList, 'Home'>;
93
+ };
94
+
95
+ const Home: React.FC<AppProps> = ({ navigation }): JSX.Element => {
96
+ const theme = useTheme();
97
+ const defaultStyles = styles(theme);
98
+ const spinValue = new Animated.Value(0);
99
+ const securityHelper = useSecurityActions();
100
+
101
+ Animated.loop(
102
+ Animated.timing(spinValue, {
103
+ toValue: 1,
104
+ duration: 2500,
105
+ easing: Easing.linear,
106
+ useNativeDriver: true,
107
+ })
108
+ ).start();
109
+
110
+ const spin = spinValue.interpolate({
111
+ inputRange: [0, 1],
112
+ outputRange: ['0deg', '360deg'],
113
+ });
114
+
115
+ const changePassword = (): void => {
116
+ securityHelper.showChangePassword();
117
+ };
118
+
119
+ const logOut = (): void => {
120
+ LocalStorage.clearAuthCredentials();
121
+ securityHelper.onUserNotAuthenticated();
122
+ };
123
+
124
+ const menuItems: InfoListItemProps[] = [
125
+ { title: 'Change Password', icon: LockIcon, onPress: (): void => changePassword() },
126
+ { title: 'Log Out', icon: ExitToAppIcon, onPress: (): void => logOut() },
127
+ ];
128
+
129
+ return (
130
+ <>
131
+ <Header
132
+ title={'Home Page'}
133
+ icon={MenuIcon}
134
+ onIconPress={(): void => {
135
+ navigation.openDrawer();
136
+ }}
137
+ actionItems={[
138
+ {
139
+ component: (
140
+ <UserMenu
141
+ menuItems={menuItems}
142
+ avatar={
143
+ <Avatar.Text
144
+ label="UN"
145
+ size={40}
146
+ color={Colors.blue[500]}
147
+ style={{ backgroundColor: Colors.blue[50] }}
148
+ />
149
+ }
150
+ />
151
+ ),
152
+ },
153
+ ]}
154
+ />
155
+ <SafeAreaView style={defaultStyles.content}>
156
+ <ScrollView>
157
+ <View style={defaultStyles.pxbLogoWrapper}>
158
+ <Animated.View style={[defaultStyles.pxbLogo, { transform: [{ rotate: spin }] }]}>
159
+ <Logo height={100} width={100} fill={'#007bc1'} />
160
+ </Animated.View>
161
+ </View>
162
+ <H4 style={defaultStyles.title}>
163
+ Welcome to Brightlayer <H4 color={'primary'}>UI</H4>.
164
+ </H4>
165
+ <Body1 style={defaultStyles.subtitle}>
166
+ Edit <Body1 style={defaultStyles.bold}>screens/home.tsx</Body1> and save to reload.
167
+ </Body1>
168
+ <Divider style={defaultStyles.divider} />
169
+ <OpenURLButton title={'Brightlayer UI Documentation'} url={'https://brightlayer-ui.github.io/'} />
170
+ <OpenURLButton
171
+ title={'React Native Getting Started Guide'}
172
+ url={'https://brightlayer-ui.github.io/development/frameworks-mobile/react-native'}
173
+ />
174
+ <OpenURLButton
175
+ title={'Design Pattern Descriptions'}
176
+ url={'https://brightlayer-ui.github.io/patterns'}
177
+ />
178
+ <OpenURLButton
179
+ title={'React Native Component Library'}
180
+ url={'https://brightlayer-ui-components.github.io/react-native/'}
181
+ />
182
+ <OpenURLButton title={'Visit Us on GitHub'} url={'https://github.com/brightlayer-ui'} />
183
+ <OpenURLButton
184
+ title={'Design Pattern Source on GitHub'}
185
+ url={'https://github.com/brightlayer-ui/react-native-design-patterns'}
186
+ />
187
+ <OpenURLButton title={'Release Roadmap'} url={'https://brightlayer-ui.github.io/roadmap'} />
188
+ <OpenURLButton
189
+ title={'Send Feedback or Suggestions'}
190
+ url={'https://brightlayer-ui.github.io/community/contactus'}
191
+ />
192
+ </ScrollView>
193
+ </SafeAreaView>
194
+ </>
195
+ );
196
+ };
197
+
198
+ export default Home;
@@ -0,0 +1,3 @@
1
+ export * from './home';
2
+ export * from './pageOne';
3
+ export * from './pageTwo';
@@ -0,0 +1,91 @@
1
+ import React from 'react';
2
+ import { SafeAreaView, ScrollView, StyleSheet, ViewStyle } from 'react-native';
3
+ import { EmptyState, Header, IconFamily, InfoListItemProps, UserMenu } from '@brightlayer-ui/react-native-components';
4
+ import { StackNavigationProp } from '@react-navigation/stack';
5
+ import { RootStackParamList } from '../navigation';
6
+ import { Avatar } from 'react-native-paper';
7
+ import { LocalStorage } from '../store/local-storage';
8
+ import { useSecurityActions } from '@brightlayer-ui/react-native-auth-workflow';
9
+ import * as Colors from '@brightlayer-ui/colors';
10
+
11
+ const Event: IconFamily = { name: 'event', direction: 'rtl' };
12
+ const MenuIcon: IconFamily = { name: 'menu', direction: 'ltr' };
13
+ const LockIcon: IconFamily = { name: 'lock', direction: 'ltr' };
14
+ const ExitToAppIcon: IconFamily = { name: 'exit-to-app', direction: 'ltr' };
15
+ const styles = (): StyleSheet.NamedStyles<{
16
+ content: ViewStyle;
17
+ scrollViewContent: ViewStyle;
18
+ }> =>
19
+ StyleSheet.create({
20
+ content: {
21
+ flex: 1,
22
+ },
23
+ scrollViewContent: {
24
+ flex: 1,
25
+ justifyContent: 'center',
26
+ alignItems: 'center',
27
+ },
28
+ });
29
+
30
+ type AppProps = {
31
+ navigation: StackNavigationProp<RootStackParamList, 'PageOne'>;
32
+ };
33
+
34
+ const PageOne: React.FC<AppProps> = ({ navigation }): JSX.Element => {
35
+ const defaultStyles = styles();
36
+ const securityHelper = useSecurityActions();
37
+
38
+ const changePassword = (): void => {
39
+ securityHelper.showChangePassword();
40
+ };
41
+
42
+ const logOut = (): void => {
43
+ LocalStorage.clearAuthCredentials();
44
+ securityHelper.onUserNotAuthenticated();
45
+ };
46
+
47
+ const menuItems: InfoListItemProps[] = [
48
+ { title: 'Change Password', icon: LockIcon, onPress: (): void => changePassword() },
49
+ { title: 'Log Out', icon: ExitToAppIcon, onPress: (): void => logOut() },
50
+ ];
51
+
52
+ return (
53
+ <>
54
+ <Header
55
+ title={'Page One'}
56
+ icon={MenuIcon}
57
+ onIconPress={(): void => {
58
+ navigation.openDrawer();
59
+ }}
60
+ actionItems={[
61
+ {
62
+ component: (
63
+ <UserMenu
64
+ menuItems={menuItems}
65
+ avatar={
66
+ <Avatar.Text
67
+ label="UN"
68
+ size={40}
69
+ color={Colors.blue[500]}
70
+ style={{ backgroundColor: Colors.blue[50] }}
71
+ />
72
+ }
73
+ />
74
+ ),
75
+ },
76
+ ]}
77
+ />
78
+ <SafeAreaView style={defaultStyles.content}>
79
+ <ScrollView contentContainerStyle={defaultStyles.scrollViewContent}>
80
+ <EmptyState
81
+ icon={Event}
82
+ title={'Coming Soon'}
83
+ description={'Replace this page with your own content'}
84
+ />
85
+ </ScrollView>
86
+ </SafeAreaView>
87
+ </>
88
+ );
89
+ };
90
+
91
+ export default PageOne;
@@ -0,0 +1,92 @@
1
+ import React from 'react';
2
+ import { SafeAreaView, ScrollView, StyleSheet, ViewStyle } from 'react-native';
3
+ import { EmptyState, Header, IconFamily, InfoListItemProps, UserMenu } from '@brightlayer-ui/react-native-components';
4
+ import { StackNavigationProp } from '@react-navigation/stack';
5
+ import { RootStackParamList } from '../navigation';
6
+ import { useSecurityActions } from '@brightlayer-ui/react-native-auth-workflow';
7
+ import { LocalStorage } from '../store/local-storage';
8
+ import { Avatar } from 'react-native-paper';
9
+ import * as Colors from '@brightlayer-ui/colors';
10
+
11
+ const Event: IconFamily = { name: 'event', direction: 'rtl' };
12
+ const MenuIcon: IconFamily = { name: 'menu', direction: 'ltr' };
13
+ const LockIcon: IconFamily = { name: 'lock', direction: 'ltr' };
14
+ const ExitToAppIcon: IconFamily = { name: 'exit-to-app', direction: 'ltr' };
15
+
16
+ const styles = (): StyleSheet.NamedStyles<{
17
+ content: ViewStyle;
18
+ scrollViewContent: ViewStyle;
19
+ }> =>
20
+ StyleSheet.create({
21
+ content: {
22
+ flex: 1,
23
+ },
24
+ scrollViewContent: {
25
+ flex: 1,
26
+ justifyContent: 'center',
27
+ alignItems: 'center',
28
+ },
29
+ });
30
+
31
+ type AppProps = {
32
+ navigation: StackNavigationProp<RootStackParamList, 'PageTwo'>;
33
+ };
34
+
35
+ const PageTwo: React.FC<AppProps> = ({ navigation }): JSX.Element => {
36
+ const defaultStyles = styles();
37
+ const securityHelper = useSecurityActions();
38
+
39
+ const changePassword = (): void => {
40
+ securityHelper.showChangePassword();
41
+ };
42
+
43
+ const logOut = (): void => {
44
+ LocalStorage.clearAuthCredentials();
45
+ securityHelper.onUserNotAuthenticated();
46
+ };
47
+
48
+ const menuItems: InfoListItemProps[] = [
49
+ { title: 'Change Password', icon: LockIcon, onPress: (): void => changePassword() },
50
+ { title: 'Log Out', icon: ExitToAppIcon, onPress: (): void => logOut() },
51
+ ];
52
+
53
+ return (
54
+ <>
55
+ <Header
56
+ title={'Page Two'}
57
+ icon={MenuIcon}
58
+ onIconPress={(): void => {
59
+ navigation.openDrawer();
60
+ }}
61
+ actionItems={[
62
+ {
63
+ component: (
64
+ <UserMenu
65
+ menuItems={menuItems}
66
+ avatar={
67
+ <Avatar.Text
68
+ label="UN"
69
+ size={40}
70
+ color={Colors.blue[500]}
71
+ style={{ backgroundColor: Colors.blue[50] }}
72
+ />
73
+ }
74
+ />
75
+ ),
76
+ },
77
+ ]}
78
+ />
79
+ <SafeAreaView style={defaultStyles.content}>
80
+ <ScrollView contentContainerStyle={defaultStyles.scrollViewContent}>
81
+ <EmptyState
82
+ icon={Event}
83
+ title={'Coming Soon'}
84
+ description={'Replace this page with your own content'}
85
+ />
86
+ </ScrollView>
87
+ </SafeAreaView>
88
+ </>
89
+ );
90
+ };
91
+
92
+ export default PageTwo;
@@ -0,0 +1,63 @@
1
+ import { LOCAL_USER_DATA, REMEMBER_ME_DATA } from '../constants';
2
+ import AsyncStorage from '@react-native-async-storage/async-storage';
3
+
4
+ // For cross compatibility pretend AsyncStorage is just window local storage
5
+ const window = {
6
+ localStorage: {
7
+ getItem: (key: string): Promise<string | null> => AsyncStorage.getItem(key),
8
+ setItem: (key: string, value: string): Promise<void> => AsyncStorage.setItem(key, value),
9
+ removeItem: (key: string): Promise<void> => AsyncStorage.removeItem(key),
10
+ },
11
+ };
12
+
13
+ type AuthData = {
14
+ userId: string | undefined;
15
+ email: string | undefined;
16
+ rememberMeData: { user: string; rememberMe: boolean };
17
+ };
18
+
19
+ async function readAuthData(): Promise<AuthData> {
20
+ const jsonUserData = (await window.localStorage.getItem(LOCAL_USER_DATA)) || '{}';
21
+ const userData = JSON.parse(jsonUserData) as {
22
+ user?: string;
23
+ userId?: string;
24
+ };
25
+ const jsonRememberMeData = (await window.localStorage.getItem(REMEMBER_ME_DATA)) || '{}';
26
+ const rememberMeData = JSON.parse(jsonRememberMeData) as {
27
+ user: string;
28
+ rememberMe: boolean;
29
+ };
30
+ return {
31
+ userId: userData.userId,
32
+ email: userData.user,
33
+ rememberMeData: rememberMeData,
34
+ };
35
+ }
36
+
37
+ function saveAuthCredentials(user: string, userId: string): void {
38
+ const userData = {
39
+ user: user,
40
+ userId: userId,
41
+ };
42
+ void window.localStorage.setItem(LOCAL_USER_DATA, JSON.stringify(userData));
43
+ }
44
+ function saveRememberMeData(user: string, rememberMe: boolean): void {
45
+ const RememberMeData = {
46
+ user: rememberMe ? user : '',
47
+ rememberMe: rememberMe,
48
+ };
49
+ void window.localStorage.setItem(REMEMBER_ME_DATA, JSON.stringify(RememberMeData));
50
+ }
51
+ function clearAuthCredentials(): void {
52
+ void window.localStorage.removeItem(LOCAL_USER_DATA);
53
+ }
54
+ function clearRememberMeData(): void {
55
+ void window.localStorage.removeItem(REMEMBER_ME_DATA);
56
+ }
57
+ export const LocalStorage = {
58
+ readAuthData,
59
+ saveAuthCredentials,
60
+ saveRememberMeData,
61
+ clearAuthCredentials,
62
+ clearRememberMeData,
63
+ };