Remove React Native template from the open source side & update docs regarding that.pull/20896/head
|
Before Width: | Height: | Size: 210 KiB After Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 4.9 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 4.4 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 3.4 MiB After Width: | Height: | Size: 728 KiB |
|
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 274 KiB After Width: | Height: | Size: 263 KiB |
|
Before Width: | Height: | Size: 3.4 MiB After Width: | Height: | Size: 538 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 6.0 KiB |
@ -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 |
|
||||
} |
|
||||
} |
|
||||
@ -1,4 +0,0 @@ |
|||||
{ |
|
||||
"12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true, |
|
||||
"40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true |
|
||||
} |
|
||||
@ -1,14 +0,0 @@ |
|||||
node_modules/ |
|
||||
.expo/ |
|
||||
dist/ |
|
||||
npm-debug.* |
|
||||
*.jks |
|
||||
*.p8 |
|
||||
*.p12 |
|
||||
*.key |
|
||||
*.mobileprovision |
|
||||
*.orig.* |
|
||||
web-build/ |
|
||||
|
|
||||
# macOS |
|
||||
.DS_Store |
|
||||
@ -1,9 +0,0 @@ |
|||||
{ |
|
||||
"trailingComma": "all", |
|
||||
"singleQuote": true, |
|
||||
"jsxSingleQuote": false, |
|
||||
"printWidth": 80, |
|
||||
"semi": true, |
|
||||
"jsxBracketSameLine": true, |
|
||||
"arrowParens": "avoid" |
|
||||
} |
|
||||
@ -1,3 +0,0 @@ |
|||||
{ |
|
||||
"recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"] |
|
||||
} |
|
||||
@ -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, |
|
||||
}, |
|
||||
}); |
|
||||
@ -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; |
|
||||
}; |
|
||||
@ -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" |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 255 B |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 1017 B |
|
Before Width: | Height: | Size: 39 KiB |
@ -1,7 +0,0 @@ |
|||||
module.exports = function(api) { |
|
||||
api.cache(true); |
|
||||
return { |
|
||||
presets: ['babel-preset-expo'], |
|
||||
plugins: ['react-native-reanimated/plugin'], |
|
||||
}; |
|
||||
}; |
|
||||
@ -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 |
|
||||
} |
|
||||
@ -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; |
|
||||
@ -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); |
|
||||
@ -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; |
|
||||
}); |
|
||||
@ -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); |
|
||||
@ -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); |
|
||||
} |
|
||||
@ -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} |
|
||||
/> |
|
||||
); |
|
||||
} |
|
||||
@ -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, |
|
||||
}, |
|
||||
}); |
|
||||
@ -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; |
|
||||
@ -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 +0,0 @@ |
|||||
export { default as FormButtons } from './FormButtons'; |
|
||||
@ -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} |
|
||||
/> |
|
||||
); |
|
||||
} |
|
||||
@ -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), |
|
||||
}), |
|
||||
}); |
|
||||
@ -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 }, |
|
||||
}); |
|
||||
@ -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), |
|
||||
}), |
|
||||
}); |
|
||||
@ -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 |
|
||||
@ -1,3 +0,0 @@ |
|||||
import React from 'react'; |
|
||||
|
|
||||
export const LocalizationContext = React.createContext(); |
|
||||
@ -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; |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
@ -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', |
|
||||
}); |
|
||||
} |
|
||||
@ -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> |
|
||||
); |
|
||||
} |
|
||||
@ -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> |
|
||||
); |
|
||||
} |
|
||||
@ -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> |
|
||||
); |
|
||||
} |
|
||||
@ -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> |
|
||||
); |
|
||||
} |
|
||||
@ -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> |
|
||||
); |
|
||||
} |
|
||||
@ -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> |
|
||||
); |
|
||||
} |
|
||||
@ -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; |
|
||||
@ -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, |
|
||||
}, |
|
||||
}); |
|
||||
@ -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; |
|
||||
@ -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, |
|
||||
}, |
|
||||
}); |
|
||||
@ -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; |
|
||||
@ -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, |
|
||||
}, |
|
||||
}); |
|
||||
@ -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; |
|
||||
@ -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; |
|
||||
@ -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, |
|
||||
}, |
|
||||
}); |
|
||||
@ -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; |
|
||||
@ -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, |
|
||||
}, |
|
||||
}); |
|
||||
@ -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, |
|
||||
}, |
|
||||
}); |
|
||||
@ -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; |
|
||||
@ -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; |
|
||||
@ -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, |
|
||||
}; |
|
||||
@ -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, |
|
||||
}; |
|
||||
@ -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, |
|
||||
}; |
|
||||
@ -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); |
|
||||
@ -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; |
|
||||
}), |
|
||||
); |
|
||||
@ -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, () => ({})), |
|
||||
); |
|
||||
@ -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; |
|
||||
}), |
|
||||
); |
|
||||
@ -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; |
|
||||
@ -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), |
|
||||
]); |
|
||||
} |
|
||||
@ -1,6 +0,0 @@ |
|||||
import { all, fork } from 'redux-saga/effects'; |
|
||||
import AppSaga from './AppSaga'; |
|
||||
|
|
||||
export function* rootSaga() { |
|
||||
yield all([fork(AppSaga)]); |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
@ -1,5 +0,0 @@ |
|||||
export function toLocalISOString(date) { |
|
||||
const timezoneOffset = date.getTimezoneOffset(); |
|
||||
|
|
||||
return new Date(date.getTime() - timezoneOffset * 60000).toISOString(); |
|
||||
} |
|
||||
@ -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); |
|
||||
}; |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||