Browse Source

Merge pull request #20891 from abpframework/remove-react-native

Remove React Native template from the open source side & update docs regarding that.
pull/20896/head
Berkan Sasmaz 2 years ago
committed by GitHub
parent
commit
10f630e008
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. BIN
      docs/en/Blog-Posts/2024-09-17-My-Impressions-at-DotNext-2024-Conference/banner.png
  2. BIN
      docs/en/Blog-Posts/2024-09-17-My-Impressions-at-DotNext-2024-Conference/boat-trip.jpg
  3. BIN
      docs/en/Blog-Posts/2024-09-17-My-Impressions-at-DotNext-2024-Conference/collage-moscow.jpg
  4. BIN
      docs/en/Blog-Posts/2024-09-17-My-Impressions-at-DotNext-2024-Conference/general-collage.jpg
  5. BIN
      docs/en/Blog-Posts/2024-09-17-My-Impressions-at-DotNext-2024-Conference/halil-talk.jpg
  6. BIN
      docs/en/Blog-Posts/2024-09-17-My-Impressions-at-DotNext-2024-Conference/my-talk-2.png
  7. BIN
      docs/en/Blog-Posts/2024-09-17-My-Impressions-at-DotNext-2024-Conference/my-talk-pro-pictures.jpg
  8. BIN
      docs/en/Blog-Posts/2024-09-17-My-Impressions-at-DotNext-2024-Conference/my-talk.png
  9. 2
      docs/en/cli/index.md
  10. 2
      docs/en/cli/new-command-samples.md
  11. BIN
      docs/en/framework/ui/angular/images/fullscreen-true.png
  12. 2
      docs/en/framework/ui/react-native/index.md
  13. 2
      docs/en/solution-templates/layered-web-application/index.md
  14. 2
      docs/en/tutorials/mobile/react-native/index.md
  15. 1
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs
  16. 8
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProjectCreationCommandBase.cs
  17. 25
      templates/app/react-native/.eslintrc.json
  18. 4
      templates/app/react-native/.expo-shared/assets.json
  19. 14
      templates/app/react-native/.gitignore
  20. 9
      templates/app/react-native/.prettierrc
  21. 3
      templates/app/react-native/.vscode/extensions.json
  22. 101
      templates/app/react-native/App.js
  23. 32
      templates/app/react-native/Environment.js
  24. 34
      templates/app/react-native/app.json
  25. BIN
      templates/app/react-native/assets/adaptive-icon.png
  26. BIN
      templates/app/react-native/assets/avatar.png
  27. BIN
      templates/app/react-native/assets/icon.png
  28. BIN
      templates/app/react-native/assets/logo.png
  29. BIN
      templates/app/react-native/assets/splash.png
  30. 7
      templates/app/react-native/babel.config.js
  31. 44
      templates/app/react-native/package.json
  32. 11
      templates/app/react-native/src/api/API.js
  33. 62
      templates/app/react-native/src/api/AccountAPI.js
  34. 30
      templates/app/react-native/src/api/ApplicationConfigurationAPI.js
  35. 27
      templates/app/react-native/src/api/IdentityAPI.js
  36. 21
      templates/app/react-native/src/api/TenantManagementAPI.js
  37. 16
      templates/app/react-native/src/components/AddIcon/AddIcon.js
  38. 143
      templates/app/react-native/src/components/DataList/DataList.js
  39. 104
      templates/app/react-native/src/components/DrawerContent/DrawerContent.js
  40. 78
      templates/app/react-native/src/components/FormButtons/FormButtons.js
  41. 1
      templates/app/react-native/src/components/FormButtons/index.js
  42. 17
      templates/app/react-native/src/components/HamburgerIcon/HamburgerIcon.js
  43. 62
      templates/app/react-native/src/components/Loading/Loading.js
  44. 22
      templates/app/react-native/src/components/LoadingButton/LoadingButton.js
  45. 143
      templates/app/react-native/src/components/TenantBox/TenantBox.js
  46. 16
      templates/app/react-native/src/components/ValidationMessage/ValidationMessage.js
  47. 3
      templates/app/react-native/src/contexts/LocalizationContext.js
  48. 17
      templates/app/react-native/src/hocs/PermissionHOC.js
  49. 16
      templates/app/react-native/src/hooks/UsePermission.js
  50. 100
      templates/app/react-native/src/interceptors/APIInterceptor.js
  51. 22
      templates/app/react-native/src/navigators/AuthNavigator.js
  52. 43
      templates/app/react-native/src/navigators/DrawerNavigator.js
  53. 17
      templates/app/react-native/src/navigators/HomeNavigator.js
  54. 41
      templates/app/react-native/src/navigators/SettingsNavigator.js
  55. 35
      templates/app/react-native/src/navigators/TenantsNavigator.js
  56. 34
      templates/app/react-native/src/navigators/UsersNavigator.js
  57. 121
      templates/app/react-native/src/screens/ChangePassword/ChangePasswordForm.js
  58. 33
      templates/app/react-native/src/screens/ChangePassword/ChangePasswordScreen.js
  59. 144
      templates/app/react-native/src/screens/CreateUpdateTenant/CreateUpdateTenantForm.js
  60. 81
      templates/app/react-native/src/screens/CreateUpdateTenant/CreateUpdateTenantScreen.js
  61. 272
      templates/app/react-native/src/screens/CreateUpdateUser/CreateUpdateUserForm.js
  62. 69
      templates/app/react-native/src/screens/CreateUpdateUser/CreateUpdateUserScreen.js
  63. 58
      templates/app/react-native/src/screens/CreateUpdateUser/UserRoles.js
  64. 30
      templates/app/react-native/src/screens/Home/HomeScreen.js
  65. 154
      templates/app/react-native/src/screens/Login/LoginScreen.js
  66. 153
      templates/app/react-native/src/screens/ManageProfile/ManageProfileForm.js
  67. 50
      templates/app/react-native/src/screens/ManageProfile/ManageProfileScreen.js
  68. 157
      templates/app/react-native/src/screens/Settings/SettingsScreen.js
  69. 51
      templates/app/react-native/src/screens/Tenants/TenantsScreen.js
  70. 62
      templates/app/react-native/src/screens/Users/UsersScreen.js
  71. 26
      templates/app/react-native/src/store/actions/AppActions.js
  72. 13
      templates/app/react-native/src/store/actions/LoadingActions.js
  73. 13
      templates/app/react-native/src/store/actions/PersistentStorageActions.js
  74. 25
      templates/app/react-native/src/store/index.js
  75. 12
      templates/app/react-native/src/store/reducers/AppReducer.js
  76. 23
      templates/app/react-native/src/store/reducers/LoadingReducer.js
  77. 17
      templates/app/react-native/src/store/reducers/PersistentStorageReducer.js
  78. 12
      templates/app/react-native/src/store/reducers/index.js
  79. 50
      templates/app/react-native/src/store/sagas/AppSaga.js
  80. 6
      templates/app/react-native/src/store/sagas/index.js
  81. 19
      templates/app/react-native/src/store/selectors/AppSelectors.js
  82. 11
      templates/app/react-native/src/store/selectors/LoadingSelectors.js
  83. 11
      templates/app/react-native/src/store/selectors/PersistentStorageSelectors.js
  84. 5
      templates/app/react-native/src/utils/DateExtensions.js
  85. 14
      templates/app/react-native/src/utils/Debounce.js
  86. 12
      templates/app/react-native/src/utils/RandomColor.js
  87. 9
      templates/app/react-native/src/utils/ReduxConnect.js
  88. 7
      templates/app/react-native/src/utils/TokenUtils.js

BIN
docs/en/Blog-Posts/2024-09-17-My-Impressions-at-DotNext-2024-Conference/banner.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 210 KiB

After

Width:  |  Height:  |  Size: 145 KiB

BIN
docs/en/Blog-Posts/2024-09-17-My-Impressions-at-DotNext-2024-Conference/boat-trip.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
docs/en/Blog-Posts/2024-09-17-My-Impressions-at-DotNext-2024-Conference/collage-moscow.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
docs/en/Blog-Posts/2024-09-17-My-Impressions-at-DotNext-2024-Conference/general-collage.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 MiB

After

Width:  |  Height:  |  Size: 728 KiB

BIN
docs/en/Blog-Posts/2024-09-17-My-Impressions-at-DotNext-2024-Conference/halil-talk.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

After

Width:  |  Height:  |  Size: 74 KiB

BIN
docs/en/Blog-Posts/2024-09-17-My-Impressions-at-DotNext-2024-Conference/my-talk-2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

After

Width:  |  Height:  |  Size: 263 KiB

BIN
docs/en/Blog-Posts/2024-09-17-My-Impressions-at-DotNext-2024-Conference/my-talk-pro-pictures.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 MiB

After

Width:  |  Height:  |  Size: 538 KiB

BIN
docs/en/Blog-Posts/2024-09-17-My-Impressions-at-DotNext-2024-Conference/my-talk.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 44 KiB

2
docs/en/cli/index.md

@ -152,7 +152,7 @@ For more samples, go to [ABP CLI Create Solution Samples](new-command-samples.md
* `--tiered`: The Auth Server project comes as a separate project and runs at a different endpoint. It separates the Auth Server from the API Host application. If not specified, you will have a single endpoint in the server side. (*Available for* ***Team*** *or higher licenses*)
* `--mobile` or `-m`: Specifies the mobile application framework. Default value is `none`. Available frameworks:
* `none`: Without any mobile application.
* `react-native`: React Native.
* `react-native`: React Native. This mobile option is only available for active **license owners**.
* `maui`: MAUI. This mobile option is only available for ABP. (*Available for* ***Team*** *or higher licenses*)
* `--database-provider` or `-d`: Specifies the database provider. Default provider is `ef`. Available providers:
* `ef`: Entity Framework Core.

2
docs/en/cli/new-command-samples.md

@ -1,6 +1,6 @@
# ABP CLI - New Solution Sample Commands
The `abp new` command creates an ABP solution or other artifacts based on an ABP template. [ABP CLI](../cli/index.md) has several parameters to create a new ABP solution. In this document we will show you some sample commands to create a new solution. All the project names are `Acme.BookStore`. Currently, the available mobile projects are `React Native` and `MAUI` mobile app. Available database providers are `Entity Framework Core` and `MongoDB`. All the commands starts with `abp new`.
The `abp new` command creates an ABP solution or other artifacts based on an ABP template. [ABP CLI](../cli/index.md) has several parameters to create a new ABP solution. In this document we will show you some sample commands to create a new solution. All the project names are `Acme.BookStore`. Currently, the available mobile projects are `React Native` and `MAUI` mobile app (they are *available for* ***Team*** *or higher licenses*). Available database providers are `Entity Framework Core` and `MongoDB`. All the commands starts with `abp new`.
## Angular

BIN
docs/en/framework/ui/angular/images/fullscreen-true.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

2
docs/en/framework/ui/react-native/index.md

@ -7,6 +7,8 @@
# Getting Started with the React Native
> React Native mobile option is *available for* ***Team*** *or higher licenses*
ABP platform provide basic [React Native](https://reactnative.dev/) startup template to develop mobile applications **integrated to your ABP based backends**.
![React Native gif](../../../images/react-native-introduction.gif)

2
docs/en/solution-templates/layered-web-application/index.md

@ -60,7 +60,7 @@ abp new Acme.BookStore -d mongodb
This template supports the following mobile application frameworks:
- `react-native`: React Native
- `react-native`: React Native (*Available for* ***Team*** *or higher licenses*)
Use the `-m` (or `--mobile`) option to specify the mobile application framework:

2
docs/en/tutorials/mobile/react-native/index.md

@ -1,5 +1,7 @@
# Mobile Application Development Tutorial - React Native
React Native mobile option is *available for* ***Team*** *or higher licenses*. Therefore, if you don't have a commercial license, it's suggested to follow the article by downloading the source code of the sample application as described in the next chapter.
## About This Tutorial
- This tutorial assumes that you have completed the [Web Application Development tutorial](../../book-store/part-01.md) and built an ABP based application named `Acme.BookStore` with [React Native](../../../framework/ui/react-native) as the mobile option.. Therefore, if you haven't completed the [Web Application Development tutorial](../../book-store/part-01.md), you either need to complete it or download the source code from down below and follow this tutorial.

1
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs

@ -220,6 +220,7 @@ public class NewCommand : ProjectCreationCommandBase, IConsoleCommand, ITransien
sb.AppendLine(" abp new Acme.BookStore -u angular -d mongodb");
sb.AppendLine(" abp new Acme.BookStore -m none");
sb.AppendLine(" abp new Acme.BookStore -m react-native");
sb.AppendLine(" abp new Acme.BookStore -m maui");
sb.AppendLine(" abp new Acme.BookStore -d mongodb");
sb.AppendLine(" abp new Acme.BookStore -d mongodb -o d:\\my-project");
sb.AppendLine(" abp new Acme.BookStore -t module");

8
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProjectCreationCommandBase.cs

@ -137,7 +137,7 @@ public abstract class ProjectCreationCommandBase
Logger.LogInformation("Public Web Site: yes");
}
var mobileApp = GetMobilePreference(commandLineArgs);
var mobileApp = GetMobilePreference(commandLineArgs, template);
if (mobileApp != MobileApp.None)
{
Logger.LogInformation("Mobile App: " + mobileApp);
@ -635,7 +635,7 @@ public abstract class ProjectCreationCommandBase
}
}
protected virtual MobileApp GetMobilePreference(CommandLineArgs commandLineArgs)
protected virtual MobileApp GetMobilePreference(CommandLineArgs commandLineArgs, string template)
{
var optionValue = commandLineArgs.Options.GetOrNull(Options.Mobile.Short, Options.Mobile.Long);
@ -644,9 +644,9 @@ public abstract class ProjectCreationCommandBase
case null:
case "none":
return MobileApp.None;
case "react-native":
case "react-native" when template is AppProTemplate.TemplateName or MicroserviceProTemplate.TemplateName:
return MobileApp.ReactNative;
case "maui":
case "maui" when template is AppProTemplate.TemplateName or MicroserviceProTemplate.TemplateName:
return MobileApp.Maui;
default:
throw new CliUsageException(ExceptionMessageHelper.GetInvalidOptionExceptionMessage("Mobile App"));

25
templates/app/react-native/.eslintrc.json

@ -1,25 +0,0 @@
{
"extends": ["airbnb", "prettier", "prettier/react"],
"parser": "babel-eslint",
"env": {
"jest": true
},
"rules": {
"no-use-before-define": 0,
"react/jsx-filename-extension": 0,
"react/prop-types": ["error", { "ignore": ["navigation", "children"] }],
"react/require-default-props": 0,
"react/jsx-props-no-spreading": 0,
"react/forbid-prop-types": 0,
"import/prefer-default-export": 0,
"comma-dangle": 0,
"no-underscore-dangle": 1,
"no-plusplus": ["error", { "allowForLoopAfterthoughts": true }],
"no-param-reassign": 0,
"operator-linebreak": 0,
"global-require": 0
},
"globals": {
"fetch": false
}
}

4
templates/app/react-native/.expo-shared/assets.json

@ -1,4 +0,0 @@
{
"12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true,
"40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true
}

14
templates/app/react-native/.gitignore

@ -1,14 +0,0 @@
node_modules/
.expo/
dist/
npm-debug.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
*.orig.*
web-build/
# macOS
.DS_Store

9
templates/app/react-native/.prettierrc

@ -1,9 +0,0 @@
{
"trailingComma": "all",
"singleQuote": true,
"jsxSingleQuote": false,
"printWidth": 80,
"semi": true,
"jsxBracketSameLine": true,
"arrowParens": "avoid"
}

3
templates/app/react-native/.vscode/extensions.json

@ -1,3 +0,0 @@
{
"recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"]
}

101
templates/app/react-native/App.js

@ -1,101 +0,0 @@
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import i18n from 'i18n-js';
import { NativeBaseProvider } from 'native-base';
import React, { useEffect, useMemo, useState } from 'react';
import { enableScreens } from 'react-native-screens';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import { getEnvVars } from './Environment';
import Loading from './src/components/Loading/Loading';
import { LocalizationContext } from './src/contexts/LocalizationContext';
import { initAPIInterceptor } from './src/interceptors/APIInterceptor';
import AuthNavigator from './src/navigators/AuthNavigator';
import DrawerNavigator from './src/navigators/DrawerNavigator';
import { persistor, store } from './src/store';
import AppActions from './src/store/actions/AppActions';
import PersistentStorageActions from './src/store/actions/PersistentStorageActions';
import { createLanguageSelector } from './src/store/selectors/AppSelectors';
import { createTokenSelector } from './src/store/selectors/PersistentStorageSelectors';
import { connectToRedux } from './src/utils/ReduxConnect';
import { isTokenValid } from './src/utils/TokenUtils';
const Stack = createNativeStackNavigator();
const { localization } = getEnvVars();
i18n.defaultSeparator = '::';
const cloneT = i18n.t;
i18n.t = (key, ...args) => {
if (key.slice(0, 2) === '::') {
key = localization.defaultResourceName + key;
}
return cloneT(key, ...args);
};
enableScreens();
initAPIInterceptor(store);
export default function App() {
const language = createLanguageSelector()(store.getState());
const [isReady, setIsReady] = useState(false);
const localizationContextValue = useMemo(
() => ({
t: i18n.t,
locale: (language || {}).cultureName,
}),
[language]
);
useEffect(() => {
store.dispatch(
AppActions.fetchAppConfigAsync({
callback: () => setIsReady(true),
showLoading: true,
})
);
}, []);
return (
<NavigationContainer>
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<NativeBaseProvider>
{isReady ? (
<LocalizationContext.Provider value={localizationContextValue}>
<ConnectedAppContainer />
</LocalizationContext.Provider>
) : null}
<Loading />
</NativeBaseProvider>
</PersistGate>
</Provider>
</NavigationContainer>
);
}
function AppContainer({token, setToken}) {
const isValid = useMemo(() => isTokenValid(token), [token]);
useEffect(() => {
if (!isValid && token && token.access_token) {
setToken({})
}
}, [isValid]);
return isValid ? <DrawerNavigator /> : <AuthNavigator />
}
const ConnectedAppContainer = connectToRedux({
component: AppContainer,
stateProps: (state) => ({
token: createTokenSelector()(state),
}),
dispatchProps: {
setToken: PersistentStorageActions.setToken,
},
});

32
templates/app/react-native/Environment.js

@ -1,32 +0,0 @@
const yourIP = 'localhost'; // See the docs https://docs.abp.io/en/abp/latest/Getting-Started-React-Native?Tiered=No
const port = 44305;
const apiUrl = `http://${yourIP}:${port}`;
const ENV = {
dev: {
apiUrl: apiUrl,
oAuthConfig: {
issuer: apiUrl,
clientId: 'MyProjectName_App',
scope: 'offline_access MyProjectName',
},
localization: {
defaultResourceName: 'MyProjectName',
},
},
prod: {
apiUrl: 'http://localhost:44305',
oAuthConfig: {
issuer: 'http://localhost:44305',
clientId: 'MyProjectName_App',
scope: 'offline_access MyProjectName',
},
localization: {
defaultResourceName: 'MyProjectName',
},
},
};
export const getEnvVars = () => {
// eslint-disable-next-line no-undef
return __DEV__ ? ENV.dev : ENV.prod;
};

34
templates/app/react-native/app.json

@ -1,34 +0,0 @@
{
"expo": {
"name": "MyProjectName",
"slug": "MyProjectName",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "cover",
"backgroundColor": "#38003c"
},
"updates": {
"fallbackToCacheTimeout": 0
},
"assetBundlePatterns": ["**/*"],
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.MyCompanyName.MyProjectName",
"buildNumber": "1.0.0"
},
"android": {
"package": "com.MyCompanyName.MyProjectName",
"versionCode": 1,
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#FFFFFF"
}
},
"web": {
"favicon": "./assets/icon.png"
}
}
}

BIN
templates/app/react-native/assets/adaptive-icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

BIN
templates/app/react-native/assets/avatar.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 255 B

BIN
templates/app/react-native/assets/icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

BIN
templates/app/react-native/assets/logo.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1017 B

BIN
templates/app/react-native/assets/splash.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

7
templates/app/react-native/babel.config.js

@ -1,7 +0,0 @@
module.exports = function(api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
plugins: ['react-native-reanimated/plugin'],
};
};

44
templates/app/react-native/package.json

@ -1,44 +0,0 @@
{
"name": "myprojectname",
"version": "1.0.0",
"main": "node_modules/expo/AppEntry.js",
"scripts": {
"start": "expo start --port 19000",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
"eject": "expo eject"
},
"dependencies": {
"@react-native-async-storage/async-storage": "1.18.2",
"@react-navigation/drawer": "^6.5.4",
"@react-navigation/native": "^6.1.0",
"@react-navigation/native-stack": "^6.9.5",
"@reduxjs/toolkit": "^1.7.1",
"axios": "~1.2.1",
"expo": "^49.0.0",
"expo-status-bar": "~1.6.0",
"formik": "^2.2.9",
"i18n-js": "^3.8.0",
"native-base": "^3.4.25",
"prop-types": "^15.8.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-native": "0.72.10",
"react-native-chart-kit": "^6.11.0",
"react-native-gesture-handler": "~2.12.0",
"react-native-reanimated": "~3.3.0",
"react-native-safe-area-context": "4.6.3",
"react-native-screens": "~3.22.0",
"react-native-svg": "13.9.0",
"react-native-web": "~0.19.6",
"react-redux": "^8.0.5",
"redux-persist": "^6.0.0",
"redux-saga": "^1.2.1",
"yup": "^0.32.11"
},
"devDependencies": {
"@babel/core": "^7.19.3"
},
"private": true
}

11
templates/app/react-native/src/api/API.js

@ -1,11 +0,0 @@
import axios from 'axios';
import { getEnvVars } from '../../Environment';
const { apiUrl } = getEnvVars();
const axiosInstance = axios.create({
baseURL: apiUrl,
withCredentials: false
});
export default axiosInstance;

62
templates/app/react-native/src/api/AccountAPI.js

@ -1,62 +0,0 @@
import api from './API';
import { getEnvVars } from '../../Environment';
const { oAuthConfig } = getEnvVars();
getLoginData = (username, password) => {
const formData = {
grant_type: 'password',
scope: oAuthConfig.scope,
username: username,
password: password,
client_id: oAuthConfig.clientId,
};
if (oAuthConfig.clientSecret)
formData['client_secret'] = oAuthConfig.clientSecret;
return Object.entries(formData)
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
.join('&');
};
export const login = ({ username, password }) =>
api({
method: 'POST',
url: '/connect/token',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
data: getLoginData(username, password),
baseURL: oAuthConfig.issuer,
}).then(({ data }) => data);
export const Logout = (
input = { client_id: '', token: '', token_type_hint: '' },
) => {
if (!input.token_type_hint) {
input.token_type_hint = 'access_token';
}
const _data = Object.entries(input)
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
.join('&');
return api({
method: 'POST',
url: '/connect/revocat',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
data: _data,
baseURL: oAuthConfig.issuer,
}).then(({ data }) => data);
};
export const getTenant = tenantName =>
api({
method: 'GET',
url: `/api/abp/multi-tenancy/tenants/by-name/${tenantName}`,
}).then(({ data }) => data);
export const getTenantById = tenantId =>
api({
method: 'GET',
url: `/api/abp/multi-tenancy/tenants/by-id/${tenantId}`,
}).then(({ data }) => data);

30
templates/app/react-native/src/api/ApplicationConfigurationAPI.js

@ -1,30 +0,0 @@
import i18n from 'i18n-js';
import api from './API';
export const getApplicationConfiguration = () =>
api
.get('/api/abp/application-configuration')
.then(({ data }) => data)
.then(async config => {
const { cultureName } = config.localization.currentCulture;
i18n.locale = cultureName;
Object.keys(config.localization.values).forEach(key => {
const resource = config.localization.values[key];
if (typeof resource !== 'object') return;
Object.keys(resource).forEach(key2 => {
if (/'{|{/g.test(resource[key2])) {
resource[key2] = resource[key2].replace(/'{|{/g, '{{').replace(/}'|}/g, '}}');
}
});
});
i18n.translations[cultureName] = {
...config.localization.values,
...(i18n.translations[cultureName] || {}),
};
return config;
});

27
templates/app/react-native/src/api/IdentityAPI.js

@ -1,27 +0,0 @@
import api from './API';
export const getAllRoles = () => api.get('/api/identity/roles/all').then(({ data }) => data.items);
export const getUserRoles = id =>
api.get(`/api/identity/users/${id}/roles`).then(({ data }) => data.items);
export const getUsers = (params = { maxResultCount: 10, skipCount: 0 }) =>
api.get('/api/identity/users', { params }).then(({ data }) => data);
export const getUserById = id => api.get(`/api/identity/users/${id}`).then(({ data }) => data);
export const createUser = body => api.post('/api/identity/users', body).then(({ data }) => data);
export const updateUser = (body, id) =>
api.put(`/api/identity/users/${id}`, body).then(({ data }) => data);
export const removeUser = id => api.delete(`/api/identity/users/${id}`);
export const getProfileDetail = () => api.get('/api/account/my-profile').then(({ data }) => data);
export const updateProfileDetail = body =>
api.put('/api/account/my-profile', body).then(({ data }) => data);
export const changePassword = body =>
api.post('/api/account/my-profile/change-password', body).then(({ data }) => data);

21
templates/app/react-native/src/api/TenantManagementAPI.js

@ -1,21 +0,0 @@
import api from './API';
export function getTenants(params = {}) {
return api.get('/api/multi-tenancy/tenants', { params }).then(({ data }) => data);
}
export function createTenant(body) {
return api.post('/api/multi-tenancy/tenants', body).then(({ data }) => data);
}
export function getTenantById(id) {
return api.get(`/api/multi-tenancy/tenants/${id}`).then(({ data }) => data);
}
export function updateTenant(body, id) {
return api.put(`/api/multi-tenancy/tenants/${id}`, body).then(({ data }) => data);
}
export function removeTenant(id) {
return api.delete(`/api/multi-tenancy/tenants/${id}`).then(({ data }) => data);
}

16
templates/app/react-native/src/components/AddIcon/AddIcon.js

@ -1,16 +0,0 @@
import { Ionicons } from '@expo/vector-icons';
import { Icon } from 'native-base';
import React from 'react';
export default function AddIcon({ onPress, ...iconProps }) {
return (
<Icon
onPress={onPress}
as={Ionicons}
name={'add'}
size="7"
marginRight={-2}
{...iconProps}
/>
);
}

143
templates/app/react-native/src/components/DataList/DataList.js

@ -1,143 +0,0 @@
import { Ionicons } from '@expo/vector-icons';
import { useFocusEffect } from '@react-navigation/native';
import i18n from 'i18n-js';
import { Box, Center, FlatList, Icon, Input, Spinner, Text } from 'native-base';
import PropTypes from 'prop-types';
import React, { forwardRef, useCallback, useEffect, useState } from 'react';
import { StyleSheet, View } from 'react-native';
import LoadingActions from '../../store/actions/LoadingActions';
import { debounce } from '../../utils/Debounce';
import { connectToRedux } from '../../utils/ReduxConnect';
import LoadingButton from '../LoadingButton/LoadingButton';
function DataList({
navigation,
fetchFn,
render,
maxResultCount = 15,
debounceTime = 350,
...props
}) {
const [records, setRecords] = useState([]);
const [totalCount, setTotalCount] = useState(0);
const [loading, setLoading] = useState(false);
const [searchLoading, setSearchLoading] = useState(false);
const [buttonLoading, setButtonLoading] = useState(false);
const [skipCount, setSkipCount] = useState(0);
const [filter, setFilter] = useState('');
const fetch = (skip = 0, isRefreshingActive = true) => {
if (isRefreshingActive) setLoading(true);
return fetchFn({ filter, maxResultCount, skipCount: skip })
.then(({ items, totalCount: total }) => {
setTotalCount(total);
setRecords(skip ? [...records, ...items] : items);
setSkipCount(skip);
})
.finally(() => {
if (isRefreshingActive) setLoading(false);
});
};
const fetchPartial = () => {
if (loading || records.length === totalCount) return;
setButtonLoading(true);
fetch(skipCount + maxResultCount, false).finally(() =>
setButtonLoading(false)
);
};
useFocusEffect(
useCallback(() => {
setSkipCount(0);
fetch(0, false);
}, [])
);
useEffect(() => {
function searchFetch() {
setSearchLoading(true);
return fetch(0, false).finally(() =>
setTimeout(() => setSearchLoading(false), 150)
);
}
debounce(searchFetch, debounceTime)();
}, [filter]);
return (
<Center>
<Box
w={{
base: '95%',
}}
mt="2"
>
<Input
placeholder={i18n.t('AbpUi::PagerSearch')}
style={{ padding: 0, margin: 0 }}
returnKeyType="done"
value={filter}
onChangeText={setFilter}
InputRightElement={
searchLoading ? (
<Spinner color="coolGray.500" marginRight={2} size="sm"/>
) : (
<Icon as={Ionicons} name={'ios-search'} size="4" marginRight={2} color="coolGray.500" />
)
}
/>
<FlatList
mt="2"
borderTopWidth="1"
borderTopColor="#e5e7eb"
data={records}
renderItem={(...args) => (
<>
{render(...args)}
{args.index + 1 === skipCount + maxResultCount &&
totalCount > records.length ? (
<View
style={{ justifyContent: 'center', alignItems: 'center' }}
>
<LoadingButton
loading={buttonLoading}
onPress={() => fetchPartial()}
>
<Text>{i18n.t('AbpUi::LoadMore')}</Text>
</LoadingButton>
</View>
) : null}
</>
)}
{...props}
/>
</Box>
</Center>
);
}
DataList.propTypes = {
...FlatList.propTypes,
fetchFn: PropTypes.func.isRequired,
render: PropTypes.func.isRequired,
maxResultCount: PropTypes.number,
debounceTime: PropTypes.number,
};
const styles = StyleSheet.create({
container: { flex: 1 },
list: {},
});
const Forwarded = forwardRef((props, ref) => (
<DataList {...props} forwardedRef={ref} />
));
export default connectToRedux({
component: Forwarded,
dispatchProps: {
startLoading: LoadingActions.start,
stopLoading: LoadingActions.stop,
},
});

104
templates/app/react-native/src/components/DrawerContent/DrawerContent.js

@ -1,104 +0,0 @@
import { Ionicons } from '@expo/vector-icons';
import Constants from 'expo-constants';
import i18n from 'i18n-js';
import { List, Text, View } from 'native-base';
import PropTypes from 'prop-types';
import React from 'react';
import { Image, StyleSheet } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { withPermission } from '../../hocs/PermissionHOC';
const screens = {
HomeStack: { label: '::Menu:Home', iconName: 'home' },
UsersStack: {
label: 'AbpIdentity::Users',
iconName: 'people',
requiredPolicy: 'AbpIdentity.Users',
},
TenantsStack: {
label: 'AbpTenantManagement::Tenants',
iconName: 'book-outline',
requiredPolicy: 'AbpTenantManagement.Tenants',
},
SettingsStack: { label: 'AbpSettingManagement::Settings', iconName: 'cog' },
};
const ListItemWithPermission = withPermission(List.Item);
function DrawerContent({
navigation,
state: { routeNames, index: currentScreenIndex },
}) {
const navigate = (screen) => {
navigation.navigate(screen);
navigation.closeDrawer();
};
return (
<View style={styles.container}>
<SafeAreaView
style={styles.container}
forceInset={{ top: 'always', horizontal: 'never' }}
>
<View style={styles.headerView}>
<Image
style={styles.logo}
source={require('../../../assets/logo.png')}
/>
</View>
<List my={2} py={0}>
{routeNames.map((name) => (
<ListItemWithPermission
key={name}
policyKey={screens[name].requiredPolicy}
bg={name === routeNames[currentScreenIndex] ? 'primary.400': 'transparent'}
onPress={() => navigate(name)}
my="0"
>
<List.Icon as={Ionicons} name={screens[name].iconName} size="7"/>
{i18n.t(screens[name].label)}
</ListItemWithPermission>
))}
</List>
</SafeAreaView>
<View style={styles.footer}>
<Text note style={styles.copyRight}>
© MyProjectName
</Text>
<Text note style={styles.version}>
v{Constants.expoConfig.version}
</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flexGrow: 1,
},
logo: {
marginTop: 20,
marginBottom: 15,
},
headerView: {
alignItems: 'center',
},
footer: {
backgroundColor: '#eee',
flexDirection: 'row',
justifyContent: 'space-between',
},
copyRight: {
margin: 15,
},
version: {
margin: 15,
},
});
DrawerContent.propTypes = {
state: PropTypes.object.isRequired,
};
export default DrawerContent;

78
templates/app/react-native/src/components/FormButtons/FormButtons.js

@ -1,78 +0,0 @@
import i18n from 'i18n-js';
import { Button, Text } from 'native-base';
import PropTypes from 'prop-types';
import React, { forwardRef } from 'react';
import { Alert, StyleSheet, View } from 'react-native';
function FormButtons({
submit,
remove,
removeMessage,
isRemoveDisabled,
isSubmitDisabled,
isShowRemove = false,
isShowSubmit = true,
}) {
const confirmation = () => {
Alert.alert(
i18n.t('AbpUi::AreYouSure'),
removeMessage,
[
{
text: i18n.t('AbpUi::Cancel'),
style: 'cancel',
},
{ text: i18n.t('AbpUi::Yes'), onPress: () => remove() },
],
{ cancelable: true },
);
};
return (
<View style={styles.container}>
{isShowRemove ? (
<Button
bg="danger.500"
style={{ flex: 1, borderRadius: 0 }}
onPress={() => confirmation()}
disabled={isRemoveDisabled}>
<Text>{i18n.t('AbpIdentity::Delete')}</Text>
</Button>
) : null}
{isShowSubmit ? (
<Button
style={{ flex: 1, borderRadius: 0 }}
onPress={submit}
disabled={isSubmitDisabled}>
<Text>{i18n.t('AbpIdentity::Save')}</Text>
</Button>
) : null}
</View>
);
}
FormButtons.propTypes = {
submit: PropTypes.func.isRequired,
remove: PropTypes.func,
removeMessage: PropTypes.string,
style: PropTypes.any,
isRemoveDisabled: PropTypes.bool,
isSubmitDisabled: PropTypes.bool,
isShowRemove: PropTypes.bool,
isShowSubmit: PropTypes.bool,
};
const styles = StyleSheet.create({
container: {
width: '100%',
justifyContent: 'center',
alignItems: 'center',
position: 'absolute',
bottom: 0,
flexDirection: 'row',
},
});
const Forwarded = forwardRef((props, ref) => <FormButtons {...props} forwardedRef={ref} />);
export default Forwarded;

1
templates/app/react-native/src/components/FormButtons/index.js

@ -1 +0,0 @@
export { default as FormButtons } from './FormButtons';

17
templates/app/react-native/src/components/HamburgerIcon/HamburgerIcon.js

@ -1,17 +0,0 @@
import { Ionicons } from '@expo/vector-icons';
import { Icon } from 'native-base';
import React from 'react';
import { Platform } from 'react-native';
export default function HamburgerIcon({ navigation, ...iconProps }) {
return (
<Icon
onPress={() => navigation.openDrawer()}
as={Ionicons}
name={Platform.OS ? 'ios-menu' : 'md-menu'}
size="8"
marginLeft={2}
{...iconProps}
/>
);
}

62
templates/app/react-native/src/components/Loading/Loading.js

@ -1,62 +0,0 @@
import { Spinner, View } from 'native-base';
import PropTypes from 'prop-types';
import React, { forwardRef } from 'react';
import { StyleSheet } from 'react-native';
import {
createLoadingSelector,
createOpacitySelector
} from '../../store/selectors/LoadingSelectors';
import { connectToRedux } from '../../utils/ReduxConnect';
function Loading({ loading, opacity }) {
return loading ? (
<View style={styles.container}>
<View
style={{
...styles.backdrop,
opacity: opacity || 0.6,
}}
/>
<Spinner style={styles.spinner} color={styles.spinner.color} />
</View>
) : null;
}
const Forwarded = forwardRef((props, ref) => <Loading {...props} forwardedRef={ref} />);
const backdropStyle = {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
backgroundColor: '#fff',
};
export const styles = StyleSheet.create({
container: {
...backdropStyle,
backgroundColor: 'transparent',
// zIndex: activeTheme.zIndex.indicator, // TODO
alignItems: 'center',
justifyContent: 'center',
},
backdrop: backdropStyle,
spinner: {
// color: activeTheme.brandPrimary, // TODO
fontSize: 100,
},
});
Loading.propTypes = {
style: PropTypes.objectOf(PropTypes.any),
loading: PropTypes.bool,
opacity: PropTypes.number,
};
export default connectToRedux({
component: Forwarded,
stateProps: state => ({
loading: createLoadingSelector()(state),
opacity: createOpacitySelector()(state),
}),
});

22
templates/app/react-native/src/components/LoadingButton/LoadingButton.js

@ -1,22 +0,0 @@
import { Button, Spinner } from 'native-base';
import PropTypes from 'prop-types';
import React from 'react';
import { StyleSheet } from 'react-native';
export default function LoadingButton({ loading = false, style, children, ...props }) {
return (
<Button style={styles.button} {...props}>
{children}
{loading ? <Spinner /> : null}
</Button>
);
}
LoadingButton.propTypes = {
...Button.propTypes,
loading: PropTypes.bool.isRequired,
};
const styles = StyleSheet.create({
button: { marginTop: 20, marginBottom: 30, height: 30 },
});

143
templates/app/react-native/src/components/TenantBox/TenantBox.js

@ -1,143 +0,0 @@
import i18n from 'i18n-js';
import { Box, Button, FormControl, Input, Text } from 'native-base';
import PropTypes from 'prop-types';
import React, { forwardRef, useState } from 'react';
import { Alert, StyleSheet, View } from 'react-native';
import { getTenant } from '../../api/AccountAPI';
import PersistentStorageActions from '../../store/actions/PersistentStorageActions';
import { createTenantSelector } from '../../store/selectors/PersistentStorageSelectors';
import { connectToRedux } from '../../utils/ReduxConnect';
function TenantBox({
tenant = {},
setTenant,
showTenantSelection,
toggleTenantSelection,
}) {
const [tenantName, setTenantName] = useState(tenant.name);
const findTenant = () => {
if (!tenantName) {
setTenant({});
toggleTenantSelection();
return;
}
getTenant(tenantName).then(({ success, ...data }) => {
if (!success) {
Alert.alert(
i18n.t('AbpUi::Error'),
i18n.t('AbpUiMultiTenancy::GivenTenantIsNotAvailable', {
0: tenantName,
}),
[{ text: i18n.t('AbpUi::Ok') }]
);
return;
}
setTenant(data);
toggleTenantSelection();
});
};
return (
<>
<Box
mb="5"
px="4"
w={{
base: '100%',
}}
style={{ flexDirection: 'row' }}
>
<View style={{ flex: 1 }}>
<Text style={styles.title}>
{i18n.t('AbpUiMultiTenancy::Tenant')}
</Text>
<Text style={styles.tenant}>
{tenant.name
? tenant.name
: i18n.t('AbpUiMultiTenancy::NotSelected')}
</Text>
</View>
<Button
style={{
display: !showTenantSelection ? 'flex' : 'none',
}}
onPress={() => toggleTenantSelection()}
>
{i18n.t('AbpUiMultiTenancy::Switch')}
</Button>
</Box>
{showTenantSelection ? (
<Box
px="3"
w={{
base: '100%',
}}
>
<FormControl my="2" width={350}>
<FormControl.Label>
{i18n.t('AbpUiMultiTenancy::Name')}
</FormControl.Label>
<Input
autoCapitalize="none"
value={tenantName}
onChangeText={setTenantName}
/>
</FormControl>
<Text style={styles.hint}>
{i18n.t('AbpUiMultiTenancy::SwitchTenantHint')}
</Text>
<View
style={{ flexDirection: 'row', justifyContent: 'space-between' }}
>
<Button
style={styles.button}
onPress={() => toggleTenantSelection()}
variant="outline"
>
{i18n.t('AbpAccount::Cancel')}
</Button>
<Button style={styles.button} onPress={() => findTenant()}>
{i18n.t('AbpAccount::Save')}
</Button>
</View>
</Box>
) : null}
</>
);
}
TenantBox.propTypes = {
setTenant: PropTypes.func.isRequired,
showTenantSelection: PropTypes.bool.isRequired,
toggleTenantSelection: PropTypes.func.isRequired,
tenant: PropTypes.object.isRequired,
};
const styles = StyleSheet.create({
button: { marginTop: 20, width: '49%' },
tenant: { color: '#777' },
title: {
marginRight: 10,
fontSize: 13,
fontWeight: '600',
textTransform: 'uppercase',
},
hint: { color: '#bbb', textAlign: 'left' },
});
const Forwarded = forwardRef((props, ref) => (
<TenantBox {...props} forwardedRef={ref} />
));
export default connectToRedux({
component: Forwarded,
dispatchProps: {
setTenant: PersistentStorageActions.setTenant,
},
stateProps: (state) => ({
tenant: createTenantSelector()(state),
}),
});

16
templates/app/react-native/src/components/ValidationMessage/ValidationMessage.js

@ -1,16 +0,0 @@
import i18n from 'i18n-js';
import React, { forwardRef } from 'react';
import { Text } from 'react-native';
const ValidationMessage = ({ children, ...props }) =>
children ? <Text style={styles} {...props}>{i18n.t(children)}</Text> : null;
const styles = {
fontSize: 12,
marginTop: 3,
color: '#ed2f2f',
};
const Forwarded = forwardRef((props, ref) => <ValidationMessage {...props} forwardedRef={ref} />);
export default Forwarded

3
templates/app/react-native/src/contexts/LocalizationContext.js

@ -1,3 +0,0 @@
import React from 'react';
export const LocalizationContext = React.createContext();

17
templates/app/react-native/src/hocs/PermissionHOC.js

@ -1,17 +0,0 @@
import React, { forwardRef } from 'react';
import PropTypes from 'prop-types';
import { usePermission } from '../hooks/UsePermission';
export function withPermission(Component, policyKey) {
const Forwarded = forwardRef((props, ref) => {
const isGranted =
policyKey || props.policyKey ? usePermission(policyKey || props.policyKey) : true;
return isGranted ? <Component ref={ref} {...props} /> : null;
});
Forwarded.propTypes = {
policyKey: PropTypes.string,
};
return Forwarded;
}

16
templates/app/react-native/src/hooks/UsePermission.js

@ -1,16 +0,0 @@
import { useEffect, useState } from 'react';
import { store } from '../store';
import { createGrantedPolicySelector } from '../store/selectors/AppSelectors';
export function usePermission(key) {
const [permission, setPermission] = useState(false);
const state = store.getState();
const policy = createGrantedPolicySelector(key)(state);
useEffect(() => {
setPermission(policy);
}, [policy]);
return permission;
}

100
templates/app/react-native/src/interceptors/APIInterceptor.js

@ -1,100 +0,0 @@
import i18n from 'i18n-js';
import { Toast } from 'native-base';
import api from '../api/API';
import LoadingActions from '../store/actions/LoadingActions';
import PersistentStorageActions from '../store/actions/PersistentStorageActions';
export function initAPIInterceptor(store) {
api.interceptors.request.use(
async request => {
const {
persistentStorage: { token, language, tenant },
} = store.getState();
if (!request.headers.Authorization && token && token.access_token) {
request.headers.Authorization = `${token.token_type} ${token.access_token}`;
}
if (!request.headers['Content-Type']) {
request.headers['Content-Type'] = 'application/json';
}
if (!request.headers['Accept-Language'] && language) {
request.headers['Accept-Language'] = language;
}
if (!request.headers.__tenant && tenant && tenant.tenantId) {
request.headers.__tenant = tenant.tenantId;
}
return request;
},
error => console.error(error),
);
api.interceptors.response.use(
response => response,
error => {
store.dispatch(LoadingActions.clear());
const errorRes = error.response;
if (errorRes) {
if (errorRes.headers._abperrorformat && errorRes.status === 401) {
store.dispatch(PersistentStorageActions.setToken({}));
}
showError({ error: errorRes.data.error || {}, status: errorRes.status });
} else {
Toast.show({
title: 'An unexpected error has occurred',
isClosable: true,
duration: 10000,
backgroundColor: 'danger.500',
});
}
return Promise.reject(error);
},
);
}
function showError({ error = {}, status }) {
let message = '';
let title = i18n.t('AbpAccount::DefaultErrorMessage');
if (typeof error === 'string') {
message = error;
} else if (error.details) {
message = error.details;
title = error.message;
} else if (error.message) {
message = error.message;
} else {
switch (status) {
case 401:
title = i18n.t('AbpAccount::DefaultErrorMessage401');
message = i18n.t('AbpAccount::DefaultErrorMessage401Detail');
break;
case 403:
title = i18n.t('AbpAccount::DefaultErrorMessage403');
message = i18n.t('AbpAccount::DefaultErrorMessage403Detail');
break;
case 404:
title = i18n.t('AbpAccount::DefaultErrorMessage404');
message = i18n.t('AbpAccount::DefaultErrorMessage404Detail');
break;
case 500:
title = i18n.t('AbpAccount::500Message');
message = i18n.t('AbpAccount::InternalServerErrorMessage');
break;
default:
break;
}
}
Toast.show({
title: `${title}\n${message}`,
isClosable: true,
duration: 10000,
backgroundColor: 'danger.500',
});
}

22
templates/app/react-native/src/navigators/AuthNavigator.js

@ -1,22 +0,0 @@
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import React, { useContext } from 'react';
import { LocalizationContext } from '../contexts/LocalizationContext';
import LoginScreen from '../screens/Login/LoginScreen';
const Stack = createNativeStackNavigator();
export default function AuthNavigator() {
const {t} = useContext(LocalizationContext);
return (
<Stack.Navigator>
<Stack.Screen
name="Login"
component={LoginScreen}
options={() => ({
title: t('AbpAccount::Login'),
})}
/>
</Stack.Navigator>
);
}

43
templates/app/react-native/src/navigators/DrawerNavigator.js

@ -1,43 +0,0 @@
import { createDrawerNavigator } from '@react-navigation/drawer';
import React from 'react';
import DrawerContent from '../components/DrawerContent/DrawerContent';
import HamburgerIcon from '../components/HamburgerIcon/HamburgerIcon';
import { LocalizationContext } from '../contexts/LocalizationContext';
import HomeStackNavigator from './HomeNavigator';
import SettingsStackNavigator from './SettingsNavigator';
import TenantsStackNavigator from './TenantsNavigator';
import UsersStackNavigator from './UsersNavigator';
const Drawer = createDrawerNavigator();
export default function DrawerNavigator() {
const { t } = React.useContext(LocalizationContext);
return (
<Drawer.Navigator initialRouteName="Home" drawerContent={DrawerContent}>
<Drawer.Screen
name="HomeStack"
component={HomeStackNavigator}
options={({ navigation }) => ({
title: t('::Menu:Home'),
headerLeft: () => <HamburgerIcon navigation={navigation} />,
})}
/>
<Drawer.Screen
name="TenantsStack"
component={TenantsStackNavigator}
options={{ header: () => null }}
/>
<Drawer.Screen
name="UsersStack"
component={UsersStackNavigator}
options={{ header: () => null }}
/>
<Drawer.Screen
name="SettingsStack"
component={SettingsStackNavigator}
options={{ header: () => null }}
/>
</Drawer.Navigator>
);
}

17
templates/app/react-native/src/navigators/HomeNavigator.js

@ -1,17 +0,0 @@
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import React from 'react';
import HomeScreen from '../screens/Home/HomeScreen';
const Stack = createNativeStackNavigator();
export default function HomeStackNavigator() {
return (
<Stack.Navigator initialRouteName="Home">
<Stack.Screen
name="Home"
component={HomeScreen}
options={{header: () => null}}
/>
</Stack.Navigator>
);
}

41
templates/app/react-native/src/navigators/SettingsNavigator.js

@ -1,41 +0,0 @@
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import i18n from 'i18n-js';
import React from 'react';
import HamburgerIcon from '../components/HamburgerIcon/HamburgerIcon';
import { LocalizationContext } from '../contexts/LocalizationContext';
import ChangePasswordScreen from '../screens/ChangePassword/ChangePasswordScreen';
import ManageProfileScreen from '../screens/ManageProfile/ManageProfileScreen';
import SettingsScreen from '../screens/Settings/SettingsScreen';
const Stack = createNativeStackNavigator();
export default function SettingsStackNavigator() {
const { t } = React.useContext(LocalizationContext);
return (
<Stack.Navigator initialRouteName="Settings">
<Stack.Screen
name="Settings"
component={SettingsScreen}
options={({ navigation }) => ({
headerLeft: () => <HamburgerIcon navigation={navigation} marginLeft={-3} />,
title: t('AbpSettingManagement::Settings'),
})}
/>
<Stack.Screen
name="ChangePassword"
component={ChangePasswordScreen}
options={{
title: i18n.t('AbpUi::ChangePassword'),
}}
/>
<Stack.Screen
name="ManageProfile"
component={ManageProfileScreen}
options={{
title: i18n.t('AbpAccount::MyAccount'),
}}
/>
</Stack.Navigator>
);
}

35
templates/app/react-native/src/navigators/TenantsNavigator.js

@ -1,35 +0,0 @@
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import React from 'react';
import AddIcon from '../components/AddIcon/AddIcon';
import HamburgerIcon from '../components/HamburgerIcon/HamburgerIcon';
import { LocalizationContext } from '../contexts/LocalizationContext';
import CreateUpdateTenantScreen from '../screens/CreateUpdateTenant/CreateUpdateTenantScreen';
import TenantsScreen from '../screens/Tenants/TenantsScreen';
const Stack = createNativeStackNavigator();
export default function TenantsStackNavigator() {
const { t } = React.useContext(LocalizationContext);
return (
<Stack.Navigator initialRouteName="Tenants">
<Stack.Screen
name="Tenants"
component={TenantsScreen}
options={({ navigation }) => ({
title: t('AbpTenantManagement::Tenants'),
headerLeft: () => <HamburgerIcon navigation={navigation} marginLeft={-3} />,
headerRight: () => <AddIcon onPress={() => navigation.navigate('CreateUpdateTenant')}/>,
})}
/>
<Stack.Screen
name="CreateUpdateTenant"
component={CreateUpdateTenantScreen}
options={({ route }) => ({
title: t(route.params?.tenantId ? 'AbpTenantManagement::Edit' : 'AbpTenantManagement::NewTenant'),
})}
/>
</Stack.Navigator>
);
}

34
templates/app/react-native/src/navigators/UsersNavigator.js

@ -1,34 +0,0 @@
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import React from 'react';
import AddIcon from '../components/AddIcon/AddIcon';
import HamburgerIcon from '../components/HamburgerIcon/HamburgerIcon';
import { LocalizationContext } from '../contexts/LocalizationContext';
import CreateUpdateUserScreen from '../screens/CreateUpdateUser/CreateUpdateUserScreen';
import UsersScreen from '../screens/Users/UsersScreen';
const Stack = createNativeStackNavigator();
export default function UsersStackNavigator() {
const { t } = React.useContext(LocalizationContext);
return (
<Stack.Navigator initialRouteName="Users">
<Stack.Screen
name="Users"
component={UsersScreen}
options={({ navigation }) => ({
title: t('AbpIdentity::Users'),
headerLeft: () => <HamburgerIcon navigation={navigation} marginLeft={-3} />,
headerRight: () => <AddIcon onPress={() => navigation.navigate('CreateUpdateUser')}/>,
})}
/>
<Stack.Screen
name="CreateUpdateUser"
component={CreateUpdateUserScreen}
options={({ route }) => ({
title: t(route.params?.userId ? 'AbpIdentity::Edit' : 'AbpIdentity::NewUser'),
})}
/>
</Stack.Navigator>
);
}

121
templates/app/react-native/src/screens/ChangePassword/ChangePasswordForm.js

@ -1,121 +0,0 @@
import { useFormik } from 'formik';
import i18n from 'i18n-js';
import {
Box,
FormControl,
Input,
KeyboardAvoidingView,
Stack,
Icon
} from 'native-base';
import PropTypes from 'prop-types';
import React, { useRef, useState } from 'react';
import * as Yup from 'yup';
import { FormButtons } from '../../components/FormButtons';
import ValidationMessage from '../../components/ValidationMessage/ValidationMessage';
import { Ionicons } from '@expo/vector-icons';
const ValidationSchema = Yup.object().shape({
currentPassword: Yup.string().required('AbpAccount::ThisFieldIsRequired.'),
newPassword: Yup.string().required('AbpAccount::ThisFieldIsRequired.'),
});
function ChangePasswordForm({ submit, cancel }) {
const [showCurrentPassword, setShowCurrentPassword] = useState(false);
const [showNewPassword, setShowNewPassword] = useState(false);
const currentPasswordRef = useRef();
const newPasswordRef = useRef();
const onSubmit = (values) => {
submit({
...values,
newPasswordConfirm: values.newPassword,
});
};
const formik = useFormik({
enableReinitialize: true,
validationSchema: ValidationSchema,
initialValues: {
currentPassword: '',
newPassword: '',
},
onSubmit,
});
return (
<>
<Box px="3">
<FormControl isRequired my="2">
<Stack mx="4">
<FormControl.Label>
{i18n.t('AbpIdentity::DisplayName:CurrentPassword')}
</FormControl.Label>
<Input
ref={currentPasswordRef}
onSubmitEditing={() => newPasswordRef?.current?.focus()}
returnKeyType="next"
onChangeText={formik.handleChange('currentPassword')}
onBlur={formik.handleBlur('currentPassword')}
value={formik.values.currentPassword}
textContentType="password"
secureTextEntry={!showCurrentPassword}
InputRightElement={
<Icon
as={Ionicons}
size="5"
mr="2"
name={showCurrentPassword ? 'eye-off-outline' : 'eye-outline'}
onPress={() => setShowCurrentPassword(!showCurrentPassword)}
/>
}
/>
<ValidationMessage>
{formik.errors.currentPassword}
</ValidationMessage>
</Stack>
</FormControl>
<FormControl isRequired my="2">
<Stack mx="4">
<FormControl.Label>
{i18n.t('AbpIdentity::DisplayName:NewPassword')}
</FormControl.Label>
<Input
ref={newPasswordRef}
returnKeyType="done"
onChangeText={formik.handleChange('newPassword')}
onBlur={formik.handleBlur('newPassword')}
value={formik.values.newPassword}
textContentType="newPassword"
secureTextEntry={!showNewPassword}
InputRightElement={
<Icon
as={Ionicons}
size="5"
mr="2"
name={showNewPassword ? 'eye-off-outline' : 'eye-outline'}
onPress={() => setShowNewPassword(!showNewPassword)}
/>
}
/>
<ValidationMessage>{formik.errors.newPassword}</ValidationMessage>
</Stack>
</FormControl>
</Box>
<FormButtons
submit={formik.handleSubmit}
cancel={cancel}
isSubmitDisabled={!formik.isValid}
/>
</>
);
}
ChangePasswordForm.propTypes = {
submit: PropTypes.func.isRequired,
cancel: PropTypes.func.isRequired,
};
export default ChangePasswordForm;

33
templates/app/react-native/src/screens/ChangePassword/ChangePasswordScreen.js

@ -1,33 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { changePassword } from '../../api/IdentityAPI';
import LoadingActions from '../../store/actions/LoadingActions';
import { connectToRedux } from '../../utils/ReduxConnect';
import ChangePasswordForm from './ChangePasswordForm';
function ChangePasswordScreen({ navigation, startLoading, stopLoading }) {
const submit = data => {
startLoading({ key: 'changePassword' });
changePassword(data)
.then(() => {
navigation.goBack();
})
.finally(() => stopLoading({ key: 'changePassword' }));
};
return <ChangePasswordForm submit={submit} cancel={() => navigation.goBack()} />;
}
ChangePasswordScreen.propTypes = {
startLoading: PropTypes.func.isRequired,
stopLoading: PropTypes.func.isRequired,
};
export default connectToRedux({
component: ChangePasswordScreen,
dispatchProps: {
startLoading: LoadingActions.start,
stopLoading: LoadingActions.stop,
},
});

144
templates/app/react-native/src/screens/CreateUpdateTenant/CreateUpdateTenantForm.js

@ -1,144 +0,0 @@
import { Ionicons } from '@expo/vector-icons';
import { useFormik } from 'formik';
import i18n from 'i18n-js';
import { Box, FormControl, Icon, Input, Stack } from 'native-base';
import PropTypes from 'prop-types';
import React, { useRef, useState } from 'react';
import * as Yup from 'yup';
import { FormButtons } from '../../components/FormButtons';
import ValidationMessage from '../../components/ValidationMessage/ValidationMessage';
import { usePermission } from '../../hooks/UsePermission';
const validations = {
name: Yup.string().required('AbpAccount::ThisFieldIsRequired.'),
};
function CreateUpdateTenantForm({ editingTenant = {}, submit, remove }) {
const tenantNameRef = useRef();
const adminEmailRef = useRef();
const adminPasswordRef = useRef();
const [showAdminPassword, setShowAdminPassword] = useState(false);
const hasRemovePermission = usePermission('AbpTenantManagement.Tenants.Delete');
const adminEmailAddressValidation = Yup.lazy(() =>
Yup.string()
.required('AbpAccount::ThisFieldIsRequired.')
.email('AbpAccount::ThisFieldIsNotAValidEmailAddress.'),
);
const adminPasswordValidation = Yup.lazy(() =>
Yup.string().required('AbpAccount::ThisFieldIsRequired.'),
);
const onSubmit = values => {
submit({
...editingTenant,
...values,
});
};
const formik = useFormik({
enableReinitialize: true,
validationSchema: Yup.object().shape({
...validations,
...(!editingTenant.id && {
adminEmailAddress: adminEmailAddressValidation,
adminPassword: adminPasswordValidation,
}),
}),
initialValues: {
lockoutEnabled: false,
twoFactorEnabled: false,
...editingTenant,
},
onSubmit,
});
return (
<>
<Box w={{ base: '100%' }} px="3">
<FormControl isRequired my="2">
<Stack mx="4">
<FormControl.Label>{i18n.t('AbpTenantManagement::TenantName')}</FormControl.Label>
<Input
ref={tenantNameRef}
onChangeText={formik.handleChange('name')}
onBlur={formik.handleBlur('name')}
value={formik.values.name}
autoCapitalize="none"
returnKeyType="next"
/>
<ValidationMessage>{formik.errors.name}</ValidationMessage>
</Stack>
</FormControl>
{!editingTenant.id ? (
<>
<FormControl isRequired my="2">
<Stack mx="4">
<FormControl.Label>
{i18n.t('AbpTenantManagement::DisplayName:AdminEmailAddress')}
</FormControl.Label>
<Input
ref={adminEmailRef}
onChangeText={formik.handleChange('adminEmailAddress')}
onBlur={formik.handleBlur('adminEmailAddress')}
value={formik.values.adminEmailAddress}
autoCapitalize="none"
onSubmitEditing={() => adminPasswordRef?.current?.focus()}
returnKeyType="next"
/>
<ValidationMessage>{formik.errors.adminEmailAddress}</ValidationMessage>
</Stack>
</FormControl>
<FormControl isRequired my="2">
<Stack mx="4">
<FormControl.Label>
{i18n.t('AbpTenantManagement::DisplayName:AdminPassword')}
</FormControl.Label>
<Input
ref={adminPasswordRef}
returnKeyType="done"
secureTextEntry={!showAdminPassword}
onChangeText={formik.handleChange('adminPassword')}
onBlur={formik.handleBlur('adminPassword')}
value={formik.values.adminPassword}
autoCapitalize="none"
InputRightElement={
<Icon
as={Ionicons}
size="5"
mr="2"
name={showAdminPassword ? 'eye-off-outline' : 'eye-outline'}
onPress={() => setShowAdminPassword(!showAdminPassword)}
/>
}
/>
<ValidationMessage>{formik.errors.adminPassword}</ValidationMessage>
</Stack>
</FormControl>
</>
) : null}
</Box>
<FormButtons
submit={formik.handleSubmit}
remove={remove}
removeMessage={i18n.t('AbpTenantManagement::TenantDeletionConfirmationMessage', {
0: editingTenant.name,
})}
isSubmitDisabled={!formik.isValid}
isShowRemove={!!editingTenant.id && hasRemovePermission}
/>
</>
);
}
CreateUpdateTenantForm.propTypes = {
editingTenant: PropTypes.object,
submit: PropTypes.func.isRequired,
remove: PropTypes.func.isRequired,
};
export default CreateUpdateTenantForm;

81
templates/app/react-native/src/screens/CreateUpdateTenant/CreateUpdateTenantScreen.js

@ -1,81 +0,0 @@
import { useFocusEffect } from '@react-navigation/native';
import PropTypes from 'prop-types';
import React, { useCallback, useState } from 'react';
import {
createTenant,
getTenantById,
removeTenant,
updateTenant
} from '../../api/TenantManagementAPI';
import LoadingActions from '../../store/actions/LoadingActions';
import { createLoadingSelector } from '../../store/selectors/LoadingSelectors';
import { connectToRedux } from '../../utils/ReduxConnect';
import CreateUpdateTenantForm from './CreateUpdateTenantForm';
function CreateUpdateTenantScreen({ navigation, route, startLoading, stopLoading }) {
const [tenant, setTenant] = useState();
const tenantId = route.params?.tenantId;
const remove = () => {
startLoading({ key: 'removeTenant' });
removeTenant(tenantId)
.then(() => navigation.goBack())
.finally(() => stopLoading({ key: 'removeTenant' }));
};
useFocusEffect(
useCallback(() => {
if (tenantId) {
getTenantById(tenantId).then((data = {}) => setTenant(data));
}
}, []),
);
const submit = data => {
startLoading({ key: 'saveTenant' });
let request;
if (data.id) {
request = updateTenant(data, tenantId);
} else {
request = createTenant(data);
}
request
.then(() => {
navigation.goBack();
})
.finally(() => stopLoading({ key: 'saveTenant' }));
};
const renderForm = () => (
<CreateUpdateTenantForm
editingTenant={tenant}
submit={submit}
remove={remove}
/>
);
if (tenantId && tenant) {
return renderForm();
}
if (!tenantId) {
return renderForm();
}
return null;
}
CreateUpdateTenantScreen.propTypes = {
startLoading: PropTypes.func.isRequired,
stopLoading: PropTypes.func.isRequired,
};
export default connectToRedux({
component: CreateUpdateTenantScreen,
stateProps: state => ({ loading: createLoadingSelector()(state) }),
dispatchProps: {
startLoading: LoadingActions.start,
stopLoading: LoadingActions.stop,
},
});

272
templates/app/react-native/src/screens/CreateUpdateUser/CreateUpdateUserForm.js

@ -1,272 +0,0 @@
import { Ionicons } from '@expo/vector-icons';
import { useFormik } from 'formik';
import i18n from 'i18n-js';
import {
Box,
Button,
Checkbox,
FormControl,
Icon,
Input,
KeyboardAvoidingView,
Stack
} from 'native-base';
import PropTypes from 'prop-types';
import React, { useRef, useState } from 'react';
import { Platform } from 'react-native';
import * as Yup from 'yup';
import { FormButtons } from '../../components/FormButtons';
import ValidationMessage from '../../components/ValidationMessage/ValidationMessage';
import { usePermission } from '../../hooks/UsePermission';
import UserRoles from './UserRoles';
const validations = {
userName: Yup.string().required('AbpAccount::ThisFieldIsRequired.'),
email: Yup.string()
.email('AbpAccount::ThisFieldIsNotAValidEmailAddress.')
.required('AbpAccount::ThisFieldIsRequired.'),
};
let roleNames = [];
function onChangeRoles(roles) {
roleNames = roles;
}
function CreateUpdateUserForm({ editingUser = {}, submit, remove }) {
const [selectedTab, setSelectedTab] = useState(0);
const [showPassword, setShowPassword] = useState(false);
const usernameRef = useRef();
const nameRef = useRef();
const surnameRef = useRef();
const emailRef = useRef();
const phoneNumberRef = useRef();
const passwordRef = useRef();
const hasRemovePermission = usePermission('AbpIdentity.Users.Delete');
const onSubmit = (values) => {
submit({
...editingUser,
...values,
roleNames,
});
};
const passwordValidation = Yup.lazy(() => {
if (editingUser.id) {
return Yup.string();
}
return Yup.string().required('AbpAccount::ThisFieldIsRequired.');
});
const formik = useFormik({
enableReinitialize: true,
validationSchema: Yup.object().shape({
...validations,
password: passwordValidation,
}),
initialValues: {
lockoutEnabled: false,
...editingUser,
},
onSubmit,
});
return (
<>
<KeyboardAvoidingView
h={{
base: '400px',
lg: 'auto',
}}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
>
<Box w={{ base: '100%' }} px="4">
<Button.Group
colorScheme="blue"
mx={{
base: 'auto',
md: 0,
}}
size="sm"
m="2"
>
<Button
size="sm"
variant={selectedTab === 0 ? 'solid' : 'outline'}
onPress={() => setSelectedTab(0)}
>
{i18n.t('AbpIdentity::UserInformations')}
</Button>
<Button
size="sm"
variant={selectedTab === 1 ? 'solid' : 'outline'}
onPress={() => setSelectedTab(1)}
>
{i18n.t('AbpIdentity::Roles')}
</Button>
</Button.Group>
{selectedTab === 0 ? (
<>
<FormControl isRequired my="2">
<Stack mx="4">
<FormControl.Label>
{i18n.t('AbpIdentity::UserName')}
</FormControl.Label>
<Input
ref={usernameRef}
onSubmitEditing={() => nameRef.current.focus()}
returnKeyType="next"
onChangeText={formik.handleChange('userName')}
onBlur={formik.handleBlur('userName')}
value={formik.values.userName}
autoCapitalize="none"
/>
<ValidationMessage>
{formik.errors.userName}
</ValidationMessage>
</Stack>
</FormControl>
<FormControl my="2">
<Stack mx="4">
<FormControl.Label>
{i18n.t('AbpIdentity::DisplayName:Name')}
</FormControl.Label>
<Input
ref={nameRef}
onSubmitEditing={() => surnameRef.current.focus()}
returnKeyType="next"
onChangeText={formik.handleChange('name')}
onBlur={formik.handleBlur('name')}
value={formik.values.name}
/>
</Stack>
</FormControl>
<FormControl my="2">
<Stack mx="4">
<FormControl.Label>
{i18n.t('AbpIdentity::DisplayName:Surname')}
</FormControl.Label>
<Input
ref={surnameRef}
onSubmitEditing={() => emailRef.current.focus()}
returnKeyType="next"
onChangeText={formik.handleChange('surname')}
onBlur={formik.handleBlur('surname')}
value={formik.values.surname}
/>
</Stack>
</FormControl>
<FormControl isRequired my="2">
<Stack mx="4">
<FormControl.Label>
{i18n.t('AbpIdentity::EmailAddress')}
</FormControl.Label>
<Input
ref={emailRef}
onSubmitEditing={() => phoneNumberRef.current.focus()}
returnKeyType="next"
onChangeText={formik.handleChange('email')}
onBlur={formik.handleBlur('email')}
value={formik.values.email}
autoCapitalize="none"
/>
<ValidationMessage>{formik.errors.email}</ValidationMessage>
</Stack>
</FormControl>
<FormControl my="2">
<Stack mx="4">
<FormControl.Label>
{i18n.t('AbpIdentity::PhoneNumber')}
</FormControl.Label>
<Input
ref={phoneNumberRef}
onSubmitEditing={() => passwordRef?.current?.focus()}
returnKeyType={!editingUser.id ? 'next' : 'default'}
onChangeText={formik.handleChange('phoneNumber')}
onBlur={formik.handleBlur('phoneNumber')}
value={formik.values.phoneNumber}
/>
</Stack>
</FormControl>
{!editingUser.id ? (
<FormControl isRequired my="2">
<Stack mx="4">
<FormControl.Label>
{i18n.t('AbpIdentity::Password')}
</FormControl.Label>
<Input
ref={passwordRef}
secureTextEntry={!showPassword}
onChangeText={formik.handleChange('password')}
onBlur={formik.handleBlur('password')}
value={formik.values.password}
autoCapitalize="none"
InputRightElement={
<Icon
as={Ionicons}
size="5"
mr="2"
name={
showPassword ? 'eye-off-outline' : 'eye-outline'
}
onPress={() => setShowPassword(!showPassword)}
/>
}
/>
<ValidationMessage>
{formik.errors.password}
</ValidationMessage>
</Stack>
</FormControl>
) : null}
<FormControl my="2">
<Stack mx="4">
<Checkbox
isChecked={formik.values.lockoutEnabled}
onPress={() =>
formik.setFieldValue(
'lockoutEnabled',
!formik.values.lockoutEnabled
)
}
>
{i18n.t('AbpIdentity::DisplayName:LockoutEnabled')}
</Checkbox>
</Stack>
</FormControl>
</>
) : (
<UserRoles {...{ editingUser, onChangeRoles }} />
)}
</Box>
</KeyboardAvoidingView>
<FormButtons
submit={formik.handleSubmit}
remove={remove}
removeMessage={i18n.t('AbpIdentity::UserDeletionConfirmationMessage', {
0: editingUser.userName,
})}
isSubmitDisabled={!formik.isValid}
isShowRemove={!!editingUser.id && hasRemovePermission}
/>
</>
);
}
CreateUpdateUserForm.propTypes = {
editingUser: PropTypes.object,
submit: PropTypes.func.isRequired,
remove: PropTypes.func.isRequired,
};
export default CreateUpdateUserForm;

69
templates/app/react-native/src/screens/CreateUpdateUser/CreateUpdateUserScreen.js

@ -1,69 +0,0 @@
import PropTypes from 'prop-types';
import React, { useEffect, useState } from 'react';
import { createUser, getUserById, removeUser, updateUser } from '../../api/IdentityAPI';
import LoadingActions from '../../store/actions/LoadingActions';
import { createLoadingSelector } from '../../store/selectors/LoadingSelectors';
import { connectToRedux } from '../../utils/ReduxConnect';
import CreateUpdateUserForm from './CreateUpdateUserForm';
function CreateUpdateUserScreen({ navigation, route, startLoading, stopLoading }) {
const [user, setUser] = useState();
const userId = route.params?.userId;
const remove = () => {
startLoading({ key: 'remove user' });
removeUser(userId)
.then(() => navigation.goBack())
.finally(() => stopLoading({ key: 'remove user' }));
};
useEffect(() => {
if (userId) {
getUserById(userId).then((data = {}) => setUser(data));
}
}, []);
const submit = data => {
startLoading({ key: 'saveUser' });
let request;
if (data.id) {
request = updateUser(data, userId);
} else {
request = createUser(data);
}
request
.then(() => {
navigation.goBack();
})
.finally(() => stopLoading({ key: 'saveUser' }));
};
const renderForm = () => (
<CreateUpdateUserForm editingUser={user} submit={submit} remove={remove} />
);
if (userId && user) {
return renderForm();
}
if (!userId) {
return renderForm();
}
return null;
}
CreateUpdateUserScreen.propTypes = {
startLoading: PropTypes.func.isRequired,
stopLoading: PropTypes.func.isRequired,
};
export default connectToRedux({
component: CreateUpdateUserScreen,
stateProps: state => ({ loading: createLoadingSelector()(state) }),
dispatchProps: {
startLoading: LoadingActions.start,
stopLoading: LoadingActions.stop,
},
});

58
templates/app/react-native/src/screens/CreateUpdateUser/UserRoles.js

@ -1,58 +0,0 @@
import { Box, Checkbox, List } from 'native-base';
import PropTypes from 'prop-types';
import React, { useEffect, useState } from 'react';
import { getAllRoles, getUserRoles } from '../../api/IdentityAPI';
function UserRoles({ editingUser = {}, onChangeRoles }) {
const [roles, setRoles] = useState([]);
const onPress = index => {
setRoles(
roles.map((role, i) => ({
...role,
isSelected: index === i ? !role.isSelected : role.isSelected,
})),
);
};
useEffect(() => {
const requests = [getAllRoles()];
if (editingUser.id) requests.push(getUserRoles(editingUser.id));
Promise.all(requests).then(([allRoles = [], userRoles = []]) => {
setRoles(
allRoles.map(role => ({
...role,
isSelected: editingUser.id
? !!userRoles?.find(userRole => userRole?.id === role?.id)
: role.isDefault,
})),
);
});
}, []);
useEffect(() => {
onChangeRoles(roles.filter(role => role.isSelected).map(role => role.name));
}, [roles]);
return (
<Box w={{base: '100%'}} px="4">
<List borderWidth={0}>
{roles.map((role, index) => (
<List.Item key={role.id} borderBottomWidth={1} >
<Checkbox isChecked={role.isSelected} onPress={() => onPress(index)} >
{role.name}
</Checkbox>
</List.Item>
))}
</List>
</Box>
);
}
UserRoles.propTypes = {
editingUser: PropTypes.objectOf(PropTypes.any).isRequired,
onChangeRoles: PropTypes.func.isRequired,
};
export default UserRoles;

30
templates/app/react-native/src/screens/Home/HomeScreen.js

@ -1,30 +0,0 @@
import i18n from 'i18n-js';
import { Box, Center, Heading, Text } from 'native-base';
import React from 'react';
import { StyleSheet } from 'react-native';
function HomeScreen() {
return (
<Center flex={0.9} px="8">
<Box
w={{
base: '100%',
}}
>
<Heading style={styles.centeredText}> {i18n.t('::Welcome')}</Heading>
<Text style={styles.centeredText}>
{i18n.t('::LongWelcomeMessage')}
</Text>
</Box>
</Center>
);
}
const styles = StyleSheet.create({
centeredText: {
textAlign: 'center',
marginBottom: 5
},
});
export default HomeScreen;

154
templates/app/react-native/src/screens/Login/LoginScreen.js

@ -1,154 +0,0 @@
import { useFormik } from 'formik';
import i18n from 'i18n-js';
import {
Box,
Button,
Center,
FormControl, Image, Input,
Stack,
WarningOutlineIcon
} from 'native-base';
import PropTypes from 'prop-types';
import React, { useRef, useState } from 'react';
import { View } from 'react-native';
import { object, string } from 'yup';
import { login } from '../../api/AccountAPI';
import TenantBox from '../../components/TenantBox/TenantBox';
import ValidationMessage from '../../components/ValidationMessage/ValidationMessage';
import AppActions from '../../store/actions/AppActions';
import LoadingActions from '../../store/actions/LoadingActions';
import PersistentStorageActions from '../../store/actions/PersistentStorageActions';
import { connectToRedux } from '../../utils/ReduxConnect';
const ValidationSchema = object().shape({
username: string().required('AbpAccount::ThisFieldIsRequired.'),
password: string().required('AbpAccount::ThisFieldIsRequired.'),
});
function LoginScreen({ startLoading, stopLoading, setToken, fetchAppConfig }) {
const [showTenantSelection, setShowTenantSelection] = useState(false);
const passwordRef = useRef(null);
const toggleTenantSelection = () => {
setShowTenantSelection(!showTenantSelection);
};
const submit = ({ username, password }) => {
startLoading({ key: 'login' });
login({ username, password })
.then((data) =>
setToken({
...data,
expire_time: new Date().valueOf() + data.expires_in,
scope: undefined,
})
)
.then(
() =>
new Promise((resolve) =>
fetchAppConfig({
showLoading: false,
callback: () => resolve(true),
})
)
)
.finally(() => stopLoading({ key: 'login' }));
};
const formik = useFormik({
validationSchema: ValidationSchema,
initialValues: { username: '', password: '' },
onSubmit: submit,
});
return (
<Center flex={0.6} px="3">
<Box
w={{
base: '100%',
}}
mb="50"
alignItems="center"
>
<Image
alt="Image"
source={require('../../../assets/logo.png')}
/>
</Box>
<TenantBox
showTenantSelection={showTenantSelection}
toggleTenantSelection={toggleTenantSelection}
/>
<Box
w={{
base: '100%',
}}
display={showTenantSelection ? 'none' : 'flex'}
>
<FormControl isRequired my="2">
<Stack mx="4">
<FormControl.Label>
{i18n.t('AbpAccount::UserNameOrEmailAddress')}
</FormControl.Label>
<Input
onChangeText={formik.handleChange('username')}
onBlur={formik.handleBlur('username')}
value={formik.values.username}
returnKeyType="next"
autoCapitalize="none"
onSubmitEditing={() => passwordRef?.current?.focus()}
size="lg"
/>
<ValidationMessage>{formik.errors.username}</ValidationMessage>
</Stack>
</FormControl>
<FormControl isRequired my="2">
<Stack mx="4">
<FormControl.Label>
{i18n.t('AbpAccount::Password')}
</FormControl.Label>
<Input
type="password"
onChangeText={formik.handleChange('password')}
onBlur={formik.handleBlur('password')}
value={formik.values.password}
ref={passwordRef}
autoCapitalize="none"
size="lg"
/>
<FormControl.ErrorMessage
leftIcon={<WarningOutlineIcon size="xs" />}
>
{formik.errors.password}
</FormControl.ErrorMessage>
</Stack>
</FormControl>
<View style={{ marginTop: 20, alignItems: 'center' }}>
<Button onPress={formik.handleSubmit} width="30%" size="lg">
{i18n.t('AbpAccount::Login')}
</Button>
</View>
</Box>
</Center>
);
}
LoginScreen.propTypes = {
startLoading: PropTypes.func.isRequired,
stopLoading: PropTypes.func.isRequired,
setToken: PropTypes.func.isRequired,
fetchAppConfig: PropTypes.func.isRequired,
};
export default connectToRedux({
component: LoginScreen,
dispatchProps: {
startLoading: LoadingActions.start,
stopLoading: LoadingActions.stop,
fetchAppConfig: AppActions.fetchAppConfigAsync,
setToken: PersistentStorageActions.setToken,
},
});

153
templates/app/react-native/src/screens/ManageProfile/ManageProfileForm.js

@ -1,153 +0,0 @@
import { useFormik } from 'formik';
import i18n from 'i18n-js';
import {
Box,
FormControl,
Input,
KeyboardAvoidingView,
Stack,
} from 'native-base';
import PropTypes from 'prop-types';
import React, { useRef } from 'react';
import * as Yup from 'yup';
import FormButtons from '../../components/FormButtons/FormButtons';
import ValidationMessage from '../../components/ValidationMessage/ValidationMessage';
const ValidationSchema = Yup.object().shape({
userName: Yup.string().required('AbpAccount::ThisFieldIsRequired.'),
email: Yup.string()
.required('AbpAccount::ThisFieldIsRequired.')
.email('AbpAccount::ThisFieldIsNotAValidEmailAddress.'),
});
function ManageProfileForm({ editingUser = {}, submit, cancel }) {
const usernameRef = useRef();
const nameRef = useRef();
const surnameRef = useRef();
const emailRef = useRef();
const phoneNumberRef = useRef();
const onSubmit = (values) => {
submit({
...editingUser,
...values,
});
};
const formik = useFormik({
enableReinitialize: true,
validationSchema: ValidationSchema,
initialValues: {
...editingUser,
},
onSubmit,
});
return (
<>
<Box px="3">
<KeyboardAvoidingView
h={{
base: '400px',
lg: 'auto',
}}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
>
<FormControl isRequired my="2">
<Stack mx="4">
<FormControl.Label>
{i18n.t('AbpIdentity::UserName')}
</FormControl.Label>
<Input
ref={usernameRef}
onSubmitEditing={() => nameRef?.current?.focus()}
returnKeyType="next"
onChangeText={formik.handleChange('userName')}
onBlur={formik.handleBlur('userName')}
value={formik.values.userName}
/>
<ValidationMessage>{formik.errors.userName}</ValidationMessage>
</Stack>
</FormControl>
<FormControl my="2">
<Stack mx="4">
<FormControl.Label>
{i18n.t('AbpIdentity::DisplayName:Name')}
</FormControl.Label>
<Input
ref={nameRef}
onSubmitEditing={() => surnameRef?.current?.focus()}
returnKeyType="next"
onChangeText={formik.handleChange('name')}
onBlur={formik.handleBlur('name')}
value={formik.values.name}
/>
</Stack>
</FormControl>
<FormControl my="2">
<Stack mx="4">
<FormControl.Label>
{i18n.t('AbpIdentity::DisplayName:Surname')}
</FormControl.Label>
<Input
ref={surnameRef}
onSubmitEditing={() => phoneNumberRef?.current?.focus()}
returnKeyType="next"
onChangeText={formik.handleChange('surname')}
onBlur={formik.handleBlur('surname')}
value={formik.values.surname}
/>
</Stack>
</FormControl>
<FormControl my="2">
<Stack mx="4">
<FormControl.Label>
{i18n.t('AbpIdentity::PhoneNumber')}
</FormControl.Label>
<Input
ref={phoneNumberRef}
onSubmitEditing={() => emailRef?.current?.focus()}
returnKeyType="next"
onChangeText={formik.handleChange('phoneNumber')}
onBlur={formik.handleBlur('phoneNumber')}
value={formik.values.phoneNumber}
/>
</Stack>
</FormControl>
<FormControl isRequired my="2">
<Stack mx="4">
<FormControl.Label>
{i18n.t('AbpIdentity::EmailAddress')}
</FormControl.Label>
<Input
ref={emailRef}
returnKeyType="done"
onChangeText={formik.handleChange('email')}
onBlur={formik.handleBlur('email')}
value={formik.values.email}
/>
<ValidationMessage>{formik.errors.email}</ValidationMessage>
</Stack>
</FormControl>
</KeyboardAvoidingView>
</Box>
<FormButtons
submit={formik.handleSubmit}
cancel={cancel}
isSubmitDisabled={!formik.isValid}
/>
</>
);
}
ManageProfileForm.propTypes = {
editingUser: PropTypes.object.isRequired,
submit: PropTypes.func.isRequired,
cancel: PropTypes.func.isRequired,
};
export default ManageProfileForm;

50
templates/app/react-native/src/screens/ManageProfile/ManageProfileScreen.js

@ -1,50 +0,0 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { updateProfileDetail, getProfileDetail } from '../../api/IdentityAPI';
import ManageProfileForm from './ManageProfileForm';
import LoadingActions from '../../store/actions/LoadingActions';
import { connectToRedux } from '../../utils/ReduxConnect';
function ManageProfileScreen({ navigation, startLoading, stopLoading }) {
const [user, setUser] = useState();
useEffect(() => {
if (!user) {
startLoading({ key: 'manageProfile' });
getProfileDetail()
.then((data = {}) => setUser(data))
.finally(() => stopLoading({ key: 'manageProfile' }));
}
});
const submit = data => {
startLoading({ key: 'manageProfile' });
updateProfileDetail(data)
.then(() => {
navigation.goBack();
})
.finally(() => stopLoading({ key: 'manageProfile' }));
};
return (
<>
{user ? (
<ManageProfileForm editingUser={user} submit={submit} cancel={() => navigation.goBack()} />
) : null}
</>
);
}
ManageProfileScreen.propTypes = {
startLoading: PropTypes.func.isRequired,
stopLoading: PropTypes.func.isRequired,
};
export default connectToRedux({
component: ManageProfileScreen,
dispatchProps: {
startLoading: LoadingActions.start,
stopLoading: LoadingActions.stop,
},
});

157
templates/app/react-native/src/screens/Settings/SettingsScreen.js

@ -1,157 +0,0 @@
import { Ionicons } from '@expo/vector-icons';
import { useFocusEffect } from '@react-navigation/native';
import i18n from 'i18n-js';
import {
Avatar,
Button,
Divider,
FormControl,
List,
Select,
Stack,
Text,
View,
} from 'native-base';
import PropTypes from 'prop-types';
import React, { useCallback, useState } from 'react';
import { getProfileDetail } from '../../api/IdentityAPI';
import AppActions from '../../store/actions/AppActions';
import {
createLanguageSelector,
createLanguagesSelector,
} from '../../store/selectors/AppSelectors';
import { createTenantSelector } from '../../store/selectors/PersistentStorageSelectors';
import { connectToRedux } from '../../utils/ReduxConnect';
import { store } from '../../store/index';
import { getEnvVars } from '../../../Environment';
const env = getEnvVars();
function SettingsScreen({
navigation,
language,
languages,
setLanguageAsync,
logoutAsync,
tenant = {},
}) {
const [user, setUser] = useState({});
const [token, setToken] = useState(store.getState().persistentStorage.token);
const fetchUser = () => {
getProfileDetail().then(data => {
setUser(data || {});
});
};
useFocusEffect(
useCallback(() => {
fetchUser();
}, []),
);
const logout = () => {
const { clientId } = env.oAuthConfig;
const { access_token, refresh_token } = token;
logoutAsync({
client_id: clientId,
token: access_token,
refresh_token: refresh_token,
});
};
return (
<View>
<List px="0" py="0" borderWidth="0">
<List.Item
style={{ backgroundColor: '#fff' }}
onPress={() => navigation.navigate('ManageProfile')}>
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
}}>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<Avatar ml="2" source={require('../../../assets/avatar.png')} />
<Text ml="2">
{tenant.name ? `${tenant.name}/` : ''}
{user.userName ? `${user.userName}` : ''}
</Text>
</View>
<List.Icon as={Ionicons} name="arrow-forward" size="5" />
</View>
</List.Item>
<Divider thickness={5} />
<List.Item
style={{ backgroundColor: '#fff' }}
onPress={() => navigation.navigate('ChangePassword')}>
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
}}>
<Text ml="2">{i18n.t('AbpUi::ChangePassword')}</Text>
<List.Icon as={Ionicons} name="arrow-forward" size="5" />
</View>
</List.Item>
<Divider thickness={5} />
<List.Item style={{ backgroundColor: '#fff' }}>
<FormControl my="2">
<Stack mx="2">
<FormControl.Label>{i18n.t('AbpUi::Language')}</FormControl.Label>
<Select
mode="dropdown"
onValueChange={setLanguageAsync}
selectedValue={language.cultureName}>
{languages.map(lang => (
<Select.Item
label={lang.displayName}
value={lang.cultureName}
key={lang.cultureName}
/>
))}
</Select>
</Stack>
</FormControl>
</List.Item>
<Divider thickness={10} />
<Button
bg="danger.500"
style={{ borderRadius: 0 }}
onPress={() => {
logout();
}}>
{i18n.t('AbpAccount::Logout')}
</Button>
</List>
</View>
);
}
SettingsScreen.propTypes = {
setLanguageAsync: PropTypes.func.isRequired,
logoutAsync: PropTypes.func.isRequired,
language: PropTypes.object.isRequired,
languages: PropTypes.array.isRequired,
tenant: PropTypes.object.isRequired,
};
export default connectToRedux({
component: SettingsScreen,
stateProps: state => ({
languages: createLanguagesSelector()(state),
language: createLanguageSelector()(state),
tenant: createTenantSelector()(state),
}),
dispatchProps: {
setLanguageAsync: AppActions.setLanguageAsync,
logoutAsync: AppActions.logoutAsync,
},
});

51
templates/app/react-native/src/screens/Tenants/TenantsScreen.js

@ -1,51 +0,0 @@
import { Box, HStack, Pressable, Text } from 'native-base';
import React from 'react';
import { getTenants } from '../../api/TenantManagementAPI';
import DataList from '../../components/DataList/DataList';
import { LocalizationContext } from '../../contexts/LocalizationContext';
function TenantsScreen({ navigation }) {
const { t } = React.useContext(LocalizationContext);
return (
<Box
w={{
base: '100%',
md: '25%',
}}
>
<DataList
navigation={navigation}
fetchFn={getTenants}
render={({ item }) => (
<Pressable
onPress={() => navigateToCreateUpdateTenantScreen(navigation, item)}
>
<Box
borderBottomWidth="1"
borderColor="coolGray.200"
pl="2"
pr="5"
py="2"
>
<HStack space={1}>
<Text color="coolGray.500">{t('AbpTenantManagement::TenantName')}:</Text>
<Text color="coolGray.800" bold>
{item.name}
</Text>
</HStack>
</Box>
</Pressable>
)}
/>
</Box>
);
}
const navigateToCreateUpdateTenantScreen = (navigation, tenant = {}) => {
navigation.navigate('CreateUpdateTenant', {
tenantId: tenant.id,
});
};
export default TenantsScreen;

62
templates/app/react-native/src/screens/Users/UsersScreen.js

@ -1,62 +0,0 @@
import { Box, HStack, Pressable, Text } from 'native-base';
import React from 'react';
import { getUsers } from '../../api/IdentityAPI';
import DataList from '../../components/DataList/DataList';
import { LocalizationContext } from '../../contexts/LocalizationContext';
function UsersScreen({ navigation }) {
const { t } = React.useContext(LocalizationContext);
return (
<DataList
navigation={navigation}
fetchFn={getUsers}
render={({item}) => (
<Pressable
onPress={() => navigateToCreateUpdateUserScreen(navigation, item)}
>
<Box
borderBottomWidth="1"
borderColor="coolGray.200"
pl="2"
pr="5"
py="2"
>
<HStack space={1}>
<Text color="coolGray.500">{t('AbpIdentity::UserName')}:</Text>
<Text color="coolGray.800" bold>
{item.userName}
</Text>
</HStack>
<HStack space={1}>
<Text color="coolGray.500">{t('AbpIdentity::EmailAddress')}:</Text>
<Text color="coolGray.800" bold>
{item.email}
</Text>
</HStack>
<HStack space={1}>
<Text color="coolGray.500">{t('AbpIdentity::DisplayName:Name')}:</Text>
<Text color="coolGray.800" bold>
{item.name}
</Text>
</HStack>
<HStack space={1}>
<Text color="coolGray.500">{t('AbpIdentity::DisplayName:Surname')}:</Text>
<Text color="coolGray.800" bold>
{item.surname}
</Text>
</HStack>
</Box>
</Pressable>
)}
/>
);
}
const navigateToCreateUpdateUserScreen = (navigation, user = {}) => {
navigation.navigate('CreateUpdateUser', {
userId: user.id,
});
};
export default UsersScreen;

26
templates/app/react-native/src/store/actions/AppActions.js

@ -1,26 +0,0 @@
import { createAction } from '@reduxjs/toolkit';
const fetchAppConfigAsync = createAction(
'app/fetchAppConfigAsync',
({ callback = () => {}, showLoading = true } = {}) => ({
payload: { callback, showLoading },
}),
);
const setAppConfig = createAction('app/setAppConfig');
const setLanguageAsync = createAction('app/setLanguageAsync');
const logoutAsync = createAction(
'app/logoutAsync',
({ client_id = '', token = '', refresh_token = '' } = {}) => ({
payload: { client_id, token, refresh_token },
}),
);
export default {
fetchAppConfigAsync,
setAppConfig,
setLanguageAsync,
logoutAsync,
};

13
templates/app/react-native/src/store/actions/LoadingActions.js

@ -1,13 +0,0 @@
import { createAction } from '@reduxjs/toolkit';
const start = createAction('loading/start');
const stop = createAction('loading/stop');
const clear = createAction('loading/clear');
export default {
start,
stop,
clear,
};

13
templates/app/react-native/src/store/actions/PersistentStorageActions.js

@ -1,13 +0,0 @@
import { createAction } from '@reduxjs/toolkit';
const setToken = createAction('persistentStorage/setToken');
const setLanguage = createAction('persistentStorage/setLanguage');
const setTenant = createAction('persistentStorage/setTenant');
export default {
setToken,
setLanguage,
setTenant,
};

25
templates/app/react-native/src/store/index.js

@ -1,25 +0,0 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import { configureStore } from '@reduxjs/toolkit';
import { persistReducer, persistStore } from 'redux-persist';
import createSagaMiddleware from 'redux-saga';
import rootReducer from './reducers';
import { rootSaga } from './sagas';
const sagaMiddleware = createSagaMiddleware();
const persistConfig = {
key: 'root',
storage: AsyncStorage,
whitelist: ['persistentStorage'],
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
export const store = configureStore({
reducer: persistedReducer,
middleware: [sagaMiddleware],
});
export const persistor = persistStore(store);
sagaMiddleware.run(rootSaga);

12
templates/app/react-native/src/store/reducers/AppReducer.js

@ -1,12 +0,0 @@
import { createReducer } from '@reduxjs/toolkit';
import AppActions from '../actions/AppActions';
const initialState = {
appConfig: {},
};
export default createReducer(initialState, builder =>
builder.addCase(AppActions.setAppConfig, (state, action) => {
state.appConfig = action.payload;
}),
);

23
templates/app/react-native/src/store/reducers/LoadingReducer.js

@ -1,23 +0,0 @@
import { createReducer } from '@reduxjs/toolkit';
import LoadingActions from '../actions/LoadingActions';
const initialState = { activeLoadings: {}, loading: false };
export default createReducer(initialState, builder =>
builder
.addCase(LoadingActions.start, (state, action) => {
const { key, opacity } = action.payload;
return {
...state,
actives: { ...state.activeLoadings, [key]: action },
loading: true,
opacity,
};
})
.addCase(LoadingActions.stop, (state, action) => {
delete state.activeLoadings[action.payload.key];
state.loading = !!Object.keys(state.activeLoadings).length;
})
.addCase(LoadingActions.clear, () => ({})),
);

17
templates/app/react-native/src/store/reducers/PersistentStorageReducer.js

@ -1,17 +0,0 @@
import { createReducer } from '@reduxjs/toolkit';
import PersistentStorageActions from '../actions/PersistentStorageActions';
const initialState = { token: {}, language: null, tenant: {} };
export default createReducer(initialState, builder =>
builder
.addCase(PersistentStorageActions.setToken, (state, action) => {
state.token = action.payload;
})
.addCase(PersistentStorageActions.setLanguage, (state, action) => {
state.language = action.payload;
})
.addCase(PersistentStorageActions.setTenant, (state, action) => {
state.tenant = action.payload;
}),
);

12
templates/app/react-native/src/store/reducers/index.js

@ -1,12 +0,0 @@
import { combineReducers } from '@reduxjs/toolkit';
import AppReducer from './AppReducer';
import LoadingReducer from './LoadingReducer';
import PersistentStorageReducer from './PersistentStorageReducer';
const rootReducer = combineReducers({
loading: LoadingReducer,
app: AppReducer,
persistentStorage: PersistentStorageReducer,
});
export default rootReducer;

50
templates/app/react-native/src/store/sagas/AppSaga.js

@ -1,50 +0,0 @@
import { all, call, put, takeLatest } from 'redux-saga/effects';
import { Logout } from '../../api/AccountAPI';
import { getApplicationConfiguration } from '../../api/ApplicationConfigurationAPI';
import AppActions from '../actions/AppActions';
import LoadingActions from '../actions/LoadingActions';
import PersistentStorageActions from '../actions/PersistentStorageActions';
function* fetchAppConfig({ payload: { showLoading, callback } }) {
if (showLoading) {
yield put(LoadingActions.start({ key: 'appConfig', opacity: 1 }));
}
const data = yield call(getApplicationConfiguration);
yield put(AppActions.setAppConfig(data));
yield put(
PersistentStorageActions.setLanguage(
data.localization.currentCulture.cultureName,
),
);
if (showLoading) yield put(LoadingActions.stop({ key: 'appConfig' }));
callback();
}
function* setLanguage(action) {
yield put(PersistentStorageActions.setLanguage(action.payload));
yield put(AppActions.fetchAppConfigAsync());
}
function* logout({ payload: { client_id, token, refresh_token } }) {
const data = { client_id, token };
yield call(Logout, data);
if (!!refresh_token) {
data.token = refresh_token;
data.token_type_hint = 'refresh_token';
yield call(Logout, data);
}
yield put(PersistentStorageActions.setToken({}));
yield put(AppActions.fetchAppConfigAsync());
}
export default function* () {
yield all([
takeLatest(AppActions.setLanguageAsync.type, setLanguage),
takeLatest(AppActions.fetchAppConfigAsync.type, fetchAppConfig),
takeLatest(AppActions.logoutAsync.type, logout),
]);
}

6
templates/app/react-native/src/store/sagas/index.js

@ -1,6 +0,0 @@
import { all, fork } from 'redux-saga/effects';
import AppSaga from './AppSaga';
export function* rootSaga() {
yield all([fork(AppSaga)]);
}

19
templates/app/react-native/src/store/selectors/AppSelectors.js

@ -1,19 +0,0 @@
import { createSelector } from 'reselect';
const getApp = state => state.app;
export function createAppConfigSelector() {
return createSelector([getApp], state => state.appConfig);
}
export function createLanguageSelector() {
return createSelector([getApp], state => state?.appConfig?.localization?.currentCulture);
}
export function createLanguagesSelector() {
return createSelector([getApp], state => state?.appConfig?.localization?.languages);
}
export function createGrantedPolicySelector(key) {
return createSelector([getApp], state => state?.appConfig?.auth?.grantedPolicies[key] ?? false);
}

11
templates/app/react-native/src/store/selectors/LoadingSelectors.js

@ -1,11 +0,0 @@
import { createSelector } from 'reselect';
const getLoading = state => state.loading;
export function createLoadingSelector() {
return createSelector([getLoading], loading => loading.loading);
}
export function createOpacitySelector() {
return createSelector([getLoading], loading => loading.opacity);
}

11
templates/app/react-native/src/store/selectors/PersistentStorageSelectors.js

@ -1,11 +0,0 @@
import { createSelector } from 'reselect';
const getPersistentStorage = state => state.persistentStorage;
export function createTokenSelector() {
return createSelector([getPersistentStorage], persistentStorage => persistentStorage.token);
}
export function createTenantSelector() {
return createSelector([getPersistentStorage], persistentStorage => persistentStorage.tenant);
}

5
templates/app/react-native/src/utils/DateExtensions.js

@ -1,5 +0,0 @@
export function toLocalISOString(date) {
const timezoneOffset = date.getTimezoneOffset();
return new Date(date.getTime() - timezoneOffset * 60000).toISOString();
}

14
templates/app/react-native/src/utils/Debounce.js

@ -1,14 +0,0 @@
export function debounce(func, wait, immediate) {
let timeout;
return (...args) => {
const context = this;
const later = () => {
timeout = null;
if (!immediate) func.apply(context, args);
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
}

12
templates/app/react-native/src/utils/RandomColor.js

@ -1,12 +0,0 @@
export function getRandomColors(count) {
const colors = [];
for (let i = 0; i < count; i++) {
const r = ((i + 5) * (i + 5) * 474) % 255;
const g = ((i + 5) * (i + 5) * 1600) % 255;
const b = ((i + 5) * (i + 5) * 84065) % 255;
colors.push(`rgba(${r}, ${g}, ${b})`);
}
return colors;
}

9
templates/app/react-native/src/utils/ReduxConnect.js

@ -1,9 +0,0 @@
import { connect } from 'react-redux';
export function connectToRedux({ component, stateProps = () => ({}), dispatchProps = () => ({}) }) {
const mapStateToProps = () => stateProps;
const mapDispatchToProps = dispatchProps;
return connect(mapStateToProps, mapDispatchToProps)(component);
}

7
templates/app/react-native/src/utils/TokenUtils.js

@ -1,7 +0,0 @@
export function isTokenValid(token) {
if (!token || typeof token !== 'object' || !token.expire_time) return false;
const now = new Date().valueOf();
return now < token.expire_time;
}
Loading…
Cancel
Save