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; |
|||
} |
|||