mirror of
https://github.com/Telecominfraproject/wlan-sdk-mobile-app.git
synced 2025-11-01 11:18:01 +00:00
Converted all components to use functional definition rather than Classes. Removed 'zustand' and replaced with 'react-redux' for state. Some minor adjustments to the project organization.
This commit is contained in:
6
index.js
6
index.js
@@ -2,8 +2,8 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {AppRegistry} from 'react-native';
|
import { AppRegistry } from 'react-native';
|
||||||
import App from './App';
|
import App from './src/App';
|
||||||
import {name as appName} from './app.json';
|
import { name as appName } from './app.json';
|
||||||
|
|
||||||
AppRegistry.registerComponent(appName, () => App);
|
AppRegistry.registerComponent(appName, () => App);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
"@react-navigation/bottom-tabs": "^6.0.9",
|
"@react-navigation/bottom-tabs": "^6.0.9",
|
||||||
"@react-navigation/native": "^6.0.6",
|
"@react-navigation/native": "^6.0.6",
|
||||||
"@react-navigation/native-stack": "^6.2.5",
|
"@react-navigation/native-stack": "^6.2.5",
|
||||||
|
"@reduxjs/toolkit": "^1.6.2",
|
||||||
"axios": "^0.23.0",
|
"axios": "^0.23.0",
|
||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
"react-native": "0.66.1",
|
"react-native": "0.66.1",
|
||||||
@@ -24,7 +25,8 @@
|
|||||||
"react-native-safe-area-context": "^3.3.2",
|
"react-native-safe-area-context": "^3.3.2",
|
||||||
"react-native-screens": "^3.8.0",
|
"react-native-screens": "^3.8.0",
|
||||||
"react-native-url-polyfill": "^1.3.0",
|
"react-native-url-polyfill": "^1.3.0",
|
||||||
"zustand": "^3.6.0"
|
"react-redux": "^7.2.6",
|
||||||
|
"redux": "^4.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.12.9",
|
"@babel/core": "^7.12.9",
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import store from './store/Store';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
import { NavigationContainer } from '@react-navigation/native';
|
import { NavigationContainer } from '@react-navigation/native';
|
||||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||||
import { Image } from 'react-native';
|
import { Image } from 'react-native';
|
||||||
|
|
||||||
import BrandSelector from './src/screens/BrandSelector';
|
import BrandSelector from './screens/BrandSelector';
|
||||||
import SignIn from './src/screens/SignIn';
|
import SignIn from './screens/SignIn';
|
||||||
import ForgotPassword from './src/screens/ForgotPassword';
|
import ForgotPassword from './screens/ForgotPassword';
|
||||||
import ResetPassword from './src/screens/ResetPassword';
|
import ResetPassword from './screens/ResetPassword';
|
||||||
import DeviceList from './src/screens/DeviceList';
|
import DeviceList from './screens/DeviceList';
|
||||||
import DeviceDetails from './src/screens/DeviceDetails';
|
import DeviceDetails from './screens/DeviceDetails';
|
||||||
import Profile from './src/screens/Profile';
|
import Profile from './screens/Profile';
|
||||||
|
|
||||||
const Stack = createNativeStackNavigator();
|
const Stack = createNativeStackNavigator();
|
||||||
const Tab = createBottomTabNavigator();
|
const Tab = createBottomTabNavigator();
|
||||||
@@ -18,15 +20,17 @@ const DeviceStack = createNativeStackNavigator();
|
|||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<NavigationContainer>
|
<Provider store={store}>
|
||||||
<Stack.Navigator>
|
<NavigationContainer>
|
||||||
<Stack.Screen name="BrandSelector" component={BrandSelector} options={{ title: 'Select Brand' }} />
|
<Stack.Navigator>
|
||||||
<Stack.Screen name="SignIn" component={SignIn} options={{ title: 'Sign In' }} />
|
<Stack.Screen name="BrandSelector" component={BrandSelector} options={{ title: 'Select Brand' }} />
|
||||||
<Stack.Screen name="ForgotPassword" component={ForgotPassword} options={{ title: 'Forgot Password' }} />
|
<Stack.Screen name="SignIn" component={SignIn} options={{ title: 'Sign In' }} />
|
||||||
<Stack.Screen name="ResetPassword" component={ResetPassword} options={{ title: 'Password Reset' }} />
|
<Stack.Screen name="ForgotPassword" component={ForgotPassword} options={{ title: 'Forgot Password' }} />
|
||||||
<Stack.Screen name="Main" component={TabScreens} options={{ headerShown: false }} />
|
<Stack.Screen name="ResetPassword" component={ResetPassword} options={{ title: 'Password Reset' }} />
|
||||||
</Stack.Navigator>
|
<Stack.Screen name="Main" component={TabScreens} options={{ headerShown: false }} />
|
||||||
</NavigationContainer>
|
</Stack.Navigator>
|
||||||
|
</NavigationContainer>
|
||||||
|
</Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +44,7 @@ function TabScreens() {
|
|||||||
headerShown: false,
|
headerShown: false,
|
||||||
tabBarIcon: ({ tintColor }) => (
|
tabBarIcon: ({ tintColor }) => (
|
||||||
<Image
|
<Image
|
||||||
source={require('./src/assets/server-solid.png')}
|
source={require('./assets/server-solid.png')}
|
||||||
style={{ width: 26, height: 26, tintColor: tintColor }}
|
style={{ width: 26, height: 26, tintColor: tintColor }}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
@@ -53,7 +57,7 @@ function TabScreens() {
|
|||||||
title: 'Profile',
|
title: 'Profile',
|
||||||
tabBarIcon: ({ tintColor }) => (
|
tabBarIcon: ({ tintColor }) => (
|
||||||
<Image
|
<Image
|
||||||
source={require('./src/assets/user-solid.png')}
|
source={require('./assets/user-solid.png')}
|
||||||
style={{ width: 26, height: 26, tintColor: tintColor }}
|
style={{ width: 26, height: 26, tintColor: tintColor }}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
@@ -1,21 +1,21 @@
|
|||||||
import { StyleSheet } from 'react-native';
|
import { StyleSheet } from 'react-native';
|
||||||
import { useStore } from './Store';
|
import store from './store/Store';
|
||||||
|
|
||||||
export function primaryColor() {
|
export var primaryColor = '#2194f3';
|
||||||
let brandInfo = useStore.getState().brandInfo;
|
export var primaryColorStyle = StyleSheet.create({});
|
||||||
|
|
||||||
|
function updatePrimaryColorInfo() {
|
||||||
|
const state = store.getState();
|
||||||
|
let brandInfo = state.brandInfo.value;
|
||||||
|
|
||||||
if (brandInfo && brandInfo.primaryColor) {
|
if (brandInfo && brandInfo.primaryColor) {
|
||||||
return brandInfo.primaryColor;
|
primaryColor = brandInfo.primaryColor;
|
||||||
|
return StyleSheet.create({
|
||||||
|
color: brandInfo.primaryColor,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return '#2194f3';
|
|
||||||
}
|
|
||||||
|
|
||||||
export function primaryColorStyle() {
|
|
||||||
return StyleSheet.create({
|
|
||||||
color: primaryColor(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
store.subscribe(updatePrimaryColorInfo);
|
||||||
|
|
||||||
export const pageStyle = StyleSheet.create({
|
export const pageStyle = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
|
|||||||
24
src/Store.js
24
src/Store.js
@@ -1,24 +0,0 @@
|
|||||||
import create from 'zustand';
|
|
||||||
|
|
||||||
export const useStore = create(set => ({
|
|
||||||
// Session information
|
|
||||||
session: null,
|
|
||||||
setSession: state => {
|
|
||||||
set({ session: state });
|
|
||||||
},
|
|
||||||
clearSession: () => set({ session: null }),
|
|
||||||
|
|
||||||
// Brand Info
|
|
||||||
brandInfo: null,
|
|
||||||
setBrandInfo: state => {
|
|
||||||
set({ brandInfo: state });
|
|
||||||
},
|
|
||||||
clearBrandInfo: () => set({ brandInfo: null }),
|
|
||||||
|
|
||||||
// System Info
|
|
||||||
systemInfo: null,
|
|
||||||
setSystemInfo: state => {
|
|
||||||
set({ systemInfo: state });
|
|
||||||
},
|
|
||||||
clearSystemInfo: () => set({ systemInfo: null }),
|
|
||||||
}));
|
|
||||||
@@ -1,19 +1,21 @@
|
|||||||
// Used the following for a basis for generating react-native from OpenAPI
|
// Used the following for a basis for generating react-native from OpenAPI
|
||||||
// https://majidlotfinia.medium.com/openapi-generator-for-react-native-by-swagger-58847cadd9e8
|
// https://majidlotfinia.medium.com/openapi-generator-for-react-native-by-swagger-58847cadd9e8
|
||||||
import 'react-native-url-polyfill/auto';
|
import 'react-native-url-polyfill/auto';
|
||||||
import {strings} from '../localization/LocalizationStrings';
|
import { strings } from '../localization/LocalizationStrings';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import {useStore} from '../Store';
|
import store from '../store/Store';
|
||||||
import {showGeneralError} from '../Utils';
|
import { setSystemInfo } from '../store/SystemInfoSlice';
|
||||||
import {AuthenticationApiFactory, Configuration as SecurityConfiguration} from './generated/owSecurityApi';
|
import { showGeneralError } from '../Utils';
|
||||||
import {DevicesApiFactory, Configuration as GatewayConfiguration} from './generated/owGatewayApi';
|
import { AuthenticationApiFactory, Configuration as SecurityConfiguration } from './generated/owSecurityApi';
|
||||||
|
import { DevicesApiFactory, Configuration as GatewayConfiguration } from './generated/owGatewayApi';
|
||||||
|
|
||||||
const axiosInstance = axios.create({});
|
const axiosInstance = axios.create({});
|
||||||
axiosInstance.interceptors.request.use(
|
axiosInstance.interceptors.request.use(
|
||||||
config => {
|
config => {
|
||||||
const session = useStore.getState().session;
|
const state = store.getState();
|
||||||
|
const session = state.session.value;
|
||||||
if (session) {
|
if (session) {
|
||||||
config.headers.Authorization = 'Bearer ' + useStore.getState().session.access_token;
|
config.headers.Authorization = 'Bearer ' + session.access_token;
|
||||||
}
|
}
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
@@ -35,19 +37,19 @@ const authenticationApi = new AuthenticationApiFactory(
|
|||||||
const gatewayConfig = new GatewayConfiguration();
|
const gatewayConfig = new GatewayConfiguration();
|
||||||
var devicesApi = null;
|
var devicesApi = null;
|
||||||
|
|
||||||
function getDevicesApi() {
|
store.subscribe(generateDevicesApi);
|
||||||
if (devicesApi === null) {
|
generateDevicesApi();
|
||||||
let url = getBaseUrlForApi('owgw');
|
|
||||||
devicesApi = url ? new DevicesApiFactory(gatewayConfig, url, axiosInstance) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return devicesApi;
|
function generateDevicesApi() {
|
||||||
|
let url = getBaseUrlForApi('owgw');
|
||||||
|
devicesApi = url ? new DevicesApiFactory(gatewayConfig, url, axiosInstance) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the base URL from the System Info. This is returned in a call to SystemInfo and it
|
// Get the base URL from the System Info. This is returned in a call to SystemInfo and it
|
||||||
// is needed in order to provide the proper base URIs for the other API systems.
|
// is needed in order to provide the proper base URIs for the other API systems.
|
||||||
function getBaseUrlForApi(type) {
|
function getBaseUrlForApi(type) {
|
||||||
const systemInfo = useStore.getState().systemInfo;
|
const state = store.getState();
|
||||||
|
const systemInfo = state.systemInfo.value;
|
||||||
|
|
||||||
if (systemInfo && systemInfo.endpoints) {
|
if (systemInfo && systemInfo.endpoints) {
|
||||||
const endpoints = systemInfo.endpoints;
|
const endpoints = systemInfo.endpoints;
|
||||||
@@ -67,7 +69,7 @@ function getBaseUrlForApi(type) {
|
|||||||
|
|
||||||
function setApiSystemInfo(systemInfo) {
|
function setApiSystemInfo(systemInfo) {
|
||||||
// Set the state, then we can use the getBaseUrlForApi to verify it has the proper information
|
// Set the state, then we can use the getBaseUrlForApi to verify it has the proper information
|
||||||
useStore.getState().setSystemInfo(systemInfo);
|
store.dispatch(setSystemInfo(systemInfo));
|
||||||
|
|
||||||
let valid = true;
|
let valid = true;
|
||||||
const typesToValidate = ['owgw']; // Include all API types that might be used
|
const typesToValidate = ['owgw']; // Include all API types that might be used
|
||||||
@@ -88,6 +90,8 @@ function setApiSystemInfo(systemInfo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleApiError(title, error) {
|
function handleApiError(title, error) {
|
||||||
|
const state = store.getState();
|
||||||
|
const session = state.session.value;
|
||||||
let message = strings.errors.unknown;
|
let message = strings.errors.unknown;
|
||||||
|
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
@@ -99,7 +103,7 @@ function handleApiError(title, error) {
|
|||||||
|
|
||||||
case 403:
|
case 403:
|
||||||
console.error(error);
|
console.error(error);
|
||||||
if (useStore.getState().session === null) {
|
if (session === null) {
|
||||||
// If not currently signed in then return a credentials error
|
// If not currently signed in then return a credentials error
|
||||||
message = strings.errors.credentials;
|
message = strings.errors.credentials;
|
||||||
} else {
|
} else {
|
||||||
@@ -128,4 +132,4 @@ function handleApiError(title, error) {
|
|||||||
showGeneralError(title, message);
|
showGeneralError(title, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {authenticationApi, getDevicesApi, handleApiError, setApiSystemInfo};
|
export { authenticationApi, devicesApi, handleApiError, setApiSystemInfo };
|
||||||
|
|||||||
@@ -1,26 +1,24 @@
|
|||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet, TouchableOpacity, View, Text, Image } from 'react-native';
|
import { StyleSheet, TouchableOpacity, View, Text, Image } from 'react-native';
|
||||||
|
|
||||||
export class BrandItem extends Component {
|
const BrandItem = props => {
|
||||||
render() {
|
const getCompanyIconUri = () => {
|
||||||
return (
|
return props.brand.iconUri;
|
||||||
<TouchableOpacity onPress={this.props.onPress}>
|
};
|
||||||
<View style={brandItemStyle.container}>
|
|
||||||
<Image style={brandItemStyle.icon} source={{ uri: this.getCompanyIconUri() }} />
|
|
||||||
<Text style={brandItemStyle.text}>{this.getCompanyName()}</Text>
|
|
||||||
</View>
|
|
||||||
</TouchableOpacity>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getCompanyIconUri() {
|
const getCompanyName = () => {
|
||||||
return this.props.brand.iconUri;
|
return props.brand.name;
|
||||||
}
|
};
|
||||||
|
|
||||||
getCompanyName() {
|
return (
|
||||||
return this.props.brand.name;
|
<TouchableOpacity onPress={props.onPress}>
|
||||||
}
|
<View style={brandItemStyle.container}>
|
||||||
}
|
<Image style={brandItemStyle.icon} source={{ uri: getCompanyIconUri() }} />
|
||||||
|
<Text style={brandItemStyle.text}>{getCompanyName()}</Text>
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const brandItemStyle = StyleSheet.create({
|
const brandItemStyle = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
@@ -41,3 +39,5 @@ const brandItemStyle = StyleSheet.create({
|
|||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export default BrandItem;
|
||||||
|
|||||||
@@ -1,42 +1,40 @@
|
|||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet, TouchableOpacity, View, Text, Image } from 'react-native';
|
import { StyleSheet, TouchableOpacity, View, Text, Image } from 'react-native';
|
||||||
|
|
||||||
export class DeviceItem extends Component {
|
const DeviceItem = props => {
|
||||||
render() {
|
const getDeviceIcon = () => {
|
||||||
return (
|
|
||||||
<TouchableOpacity onPress={this.props.onPress}>
|
|
||||||
<View style={deviceItemStyle.container}>
|
|
||||||
<Image style={deviceItemStyle.icon} source={this.getDeviceIcon()} />
|
|
||||||
|
|
||||||
<View style={deviceItemStyle.textContainer}>
|
|
||||||
<Text>{this.getDeviceName()}</Text>
|
|
||||||
<Text>{this.getDeviceType()}</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<Image style={deviceItemStyle.icon} source={this.getDeviceStatusIcon()} />
|
|
||||||
|
|
||||||
<Text>></Text>
|
|
||||||
</View>
|
|
||||||
</TouchableOpacity>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getDeviceIcon() {
|
|
||||||
return require('../assets/server-solid.png');
|
return require('../assets/server-solid.png');
|
||||||
}
|
};
|
||||||
|
|
||||||
getDeviceName() {
|
const getDeviceName = () => {
|
||||||
return this.props.device.compatible;
|
return props.device.compatible;
|
||||||
}
|
};
|
||||||
|
|
||||||
getDeviceType() {
|
const getDeviceType = () => {
|
||||||
return this.props.device.manufacturer;
|
return props.device.manufacturer;
|
||||||
}
|
};
|
||||||
|
|
||||||
getDeviceStatusIcon() {
|
const getDeviceStatusIcon = () => {
|
||||||
return require('../assets/wifi-solid.png');
|
return require('../assets/wifi-solid.png');
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
return (
|
||||||
|
<TouchableOpacity onPress={props.onPress}>
|
||||||
|
<View style={deviceItemStyle.container}>
|
||||||
|
<Image style={deviceItemStyle.icon} source={getDeviceIcon()} />
|
||||||
|
|
||||||
|
<View style={deviceItemStyle.textContainer}>
|
||||||
|
<Text>{getDeviceName()}</Text>
|
||||||
|
<Text>{getDeviceType()}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Image style={deviceItemStyle.icon} source={getDeviceStatusIcon()} />
|
||||||
|
|
||||||
|
<Text>></Text>
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const deviceItemStyle = StyleSheet.create({
|
const deviceItemStyle = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
@@ -63,3 +61,5 @@ const deviceItemStyle = StyleSheet.create({
|
|||||||
marginLeft: 10,
|
marginLeft: 10,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export default DeviceItem;
|
||||||
|
|||||||
@@ -1,92 +1,93 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { selectBrandInfo, setBrandInfo } from '../store/BrandInfoSlice';
|
||||||
import { strings } from '../localization/LocalizationStrings';
|
import { strings } from '../localization/LocalizationStrings';
|
||||||
import { useStore } from '../Store';
|
|
||||||
import { pageStyle, pageItemStyle, primaryColor } from '../AppStyle';
|
import { pageStyle, pageItemStyle, primaryColor } from '../AppStyle';
|
||||||
import { StyleSheet, View, Text, FlatList, TextInput, ActivityIndicator } from 'react-native';
|
import { StyleSheet, View, Text, FlatList, TextInput, ActivityIndicator } from 'react-native';
|
||||||
import { BrandItem } from '../components/BrandItem';
|
import BrandItem from '../components/BrandItem';
|
||||||
|
|
||||||
export default class BrandSelector extends Component {
|
const BrandSelector = props => {
|
||||||
state = {
|
const dispatch = useDispatch();
|
||||||
loading: false,
|
const brandInfo = useSelector(selectBrandInfo);
|
||||||
brands: [
|
// Currently this following state does not change, but the expectation is that this information
|
||||||
{
|
// will come from an API so it is being left as is for this development
|
||||||
id: 'openwifi',
|
const [loading, setLoading] = useState(false);
|
||||||
name: 'OpenWifi',
|
const [brands, setBrands] = useState([
|
||||||
iconUri: 'https://14oranges-ui.arilia.com/assets/14Oranges_Logo.png',
|
{
|
||||||
primaryColor: '#19255f',
|
id: 'openwifi',
|
||||||
},
|
name: 'OpenWifi',
|
||||||
{
|
iconUri: 'https://14oranges-ui.arilia.com/assets/14Oranges_Logo.png',
|
||||||
id: 'openwifigreen',
|
primaryColor: '#19255f',
|
||||||
name: 'OpenWifi (Green)',
|
},
|
||||||
iconUri: 'https://14oranges-ui.arilia.com/assets/14Oranges_Logo.png',
|
{
|
||||||
primaryColor: '#1a3e1b',
|
id: 'openwifigreen',
|
||||||
},
|
name: 'OpenWifi (Green)',
|
||||||
],
|
iconUri: 'https://14oranges-ui.arilia.com/assets/14Oranges_Logo.png',
|
||||||
filtered: false,
|
primaryColor: '#1a3e1b',
|
||||||
filteredBrands: [],
|
},
|
||||||
};
|
]);
|
||||||
|
const [filtered, setFiltered] = useState(false);
|
||||||
|
const [filteredBrands, setFilteredBrands] = useState();
|
||||||
|
|
||||||
componentDidMount() {
|
useEffect(() => {
|
||||||
if (useStore.getState().brandInfo !== null) {
|
if (brandInfo !== null) {
|
||||||
this.props.navigation.navigate('SignIn');
|
props.navigation.navigate('SignIn');
|
||||||
}
|
}
|
||||||
}
|
// No dependencies as this is only to run once on mount. There are plenty of
|
||||||
|
// hacks around this eslint warning, but disabling it makes the most sense.
|
||||||
|
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
render() {
|
const filterBrands = searchText => {
|
||||||
return (
|
|
||||||
<View style={pageStyle.container}>
|
|
||||||
<View style={pageItemStyle.container}>
|
|
||||||
<Text style={pageItemStyle.title}>{strings.brandSelector.title}</Text>
|
|
||||||
</View>
|
|
||||||
<View style={pageItemStyle.container}>
|
|
||||||
<Text style={pageItemStyle.description}>{strings.brandSelector.description}</Text>
|
|
||||||
</View>
|
|
||||||
{this.state.loading ? (
|
|
||||||
<View style={pageItemStyle.container}>
|
|
||||||
<ActivityIndicator size="large" color={primaryColor()} animating={this.state.loading} />
|
|
||||||
</View>
|
|
||||||
) : (
|
|
||||||
<View style={pageItemStyle.containerBrands}>
|
|
||||||
<View style={[pageItemStyle.container, brandingSelectorStyle.containerSearch]}>
|
|
||||||
<TextInput
|
|
||||||
style={pageItemStyle.inputText}
|
|
||||||
placeholder="Search"
|
|
||||||
onChangeText={search => this.filterBrands(search)}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<View style={pageItemStyle.container}>
|
|
||||||
<FlatList
|
|
||||||
style={brandingSelectorStyle.containerList}
|
|
||||||
data={this.state.filtered ? this.state.filteredBrands : this.state.brands}
|
|
||||||
renderItem={({ item }) => <BrandItem brand={item} onPress={this.onCompanySelect.bind(this, item)} />}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
filterBrands = searchText => {
|
|
||||||
if (searchText) {
|
if (searchText) {
|
||||||
let searchTextLowerCase = searchText.toLowerCase();
|
let searchTextLowerCase = searchText.toLowerCase();
|
||||||
this.setState({ filtered: true });
|
setFiltered(true);
|
||||||
this.setState({
|
setFilteredBrands(brands.filter(b => b.name.toLowerCase().startsWith(searchTextLowerCase)));
|
||||||
filteredBrands: this.state.brands.filter(b => b.name.toLowerCase().startsWith(searchTextLowerCase)),
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
this.setState({ filtered: false });
|
setFiltered(false);
|
||||||
this.setState({ filteredBrands: [] });
|
setFilteredBrands([]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onCompanySelect = async item => {
|
const onCompanySelect = async item => {
|
||||||
useStore.getState().setBrandInfo(item);
|
dispatch(setBrandInfo(item));
|
||||||
|
|
||||||
// Replace to the main screen. Use replace to ensure no back button
|
// Replace to the main screen. Use replace to ensure no back button
|
||||||
this.props.navigation.navigate('SignIn');
|
props.navigation.navigate('SignIn');
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
return (
|
||||||
|
<View style={pageStyle.container}>
|
||||||
|
<View style={pageItemStyle.container}>
|
||||||
|
<Text style={pageItemStyle.title}>{strings.brandSelector.title}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pageItemStyle.container}>
|
||||||
|
<Text style={pageItemStyle.description}>{strings.brandSelector.description}</Text>
|
||||||
|
</View>
|
||||||
|
{loading ? (
|
||||||
|
<View style={pageItemStyle.container}>
|
||||||
|
<ActivityIndicator size="large" color={primaryColor} animating={loading} />
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<View style={pageItemStyle.containerBrands}>
|
||||||
|
<View style={[pageItemStyle.container, brandingSelectorStyle.containerSearch]}>
|
||||||
|
<TextInput
|
||||||
|
style={pageItemStyle.inputText}
|
||||||
|
placeholder="Search"
|
||||||
|
onChangeText={search => filterBrands(search)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={pageItemStyle.container}>
|
||||||
|
<FlatList
|
||||||
|
style={brandingSelectorStyle.containerList}
|
||||||
|
data={filtered ? filteredBrands : brands}
|
||||||
|
renderItem={({ item }) => <BrandItem brand={item} onPress={() => onCompanySelect(item)} />}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const brandingSelectorStyle = StyleSheet.create({
|
const brandingSelectorStyle = StyleSheet.create({
|
||||||
containerBrands: {
|
containerBrands: {
|
||||||
@@ -104,3 +105,5 @@ const brandingSelectorStyle = StyleSheet.create({
|
|||||||
width: '100%',
|
width: '100%',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export default BrandSelector;
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import { pageStyle, pageItemStyle } from '../AppStyle';
|
import { pageStyle, pageItemStyle } from '../AppStyle';
|
||||||
import { View, Text } from 'react-native';
|
import { View, Text } from 'react-native';
|
||||||
|
|
||||||
export default class DeviceDetails extends Component {
|
const DeviceDetails = props => {
|
||||||
render() {
|
return (
|
||||||
return (
|
<View style={pageStyle.container}>
|
||||||
<View style={pageStyle.container}>
|
<View style={pageItemStyle.container}>
|
||||||
<View style={pageItemStyle.container}>
|
<Text>Device Details</Text>
|
||||||
<Text>Device Details</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
);
|
</View>
|
||||||
}
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default DeviceDetails;
|
||||||
|
|||||||
@@ -1,44 +1,41 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { strings } from '../localization/LocalizationStrings';
|
import { strings } from '../localization/LocalizationStrings';
|
||||||
import { pageStyle, pageItemStyle } from '../AppStyle';
|
import { pageStyle, pageItemStyle } from '../AppStyle';
|
||||||
import { View, Text, FlatList } from 'react-native';
|
import { View, Text, FlatList } from 'react-native';
|
||||||
import { getDevicesApi, handleApiError } from '../api/apiHandler';
|
import { devicesApi, handleApiError } from '../api/apiHandler';
|
||||||
import { DeviceItem } from '../components/DeviceItem';
|
import DeviceItem from '../components/DeviceItem';
|
||||||
|
|
||||||
export default class DeviceList extends Component {
|
const DeviceList = props => {
|
||||||
state = { devices: [] };
|
const [devices, setDevices] = useState([]);
|
||||||
|
|
||||||
render() {
|
useEffect(() => {
|
||||||
return (
|
getDevices();
|
||||||
<View style={pageStyle.container}>
|
}, []);
|
||||||
<View style={pageItemStyle.container}>
|
|
||||||
<Text style={{ fontSize: 24, fontWeight: 'bold' }}>Devices</Text>
|
|
||||||
</View>
|
|
||||||
<View style={pageItemStyle.container}>
|
|
||||||
<FlatList
|
|
||||||
data={this.state.devices}
|
|
||||||
renderItem={({ item }) => <DeviceItem device={item} onPress={this.onDevicePress} />}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
onDevicePress = async () => {
|
const onDevicePress = async () => {
|
||||||
this.props.navigation.navigate('DeviceDetails');
|
props.navigation.navigate('DeviceDetails');
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount = () => {
|
const getDevices = async () => {
|
||||||
this.getDevices();
|
|
||||||
};
|
|
||||||
|
|
||||||
getDevices = async () => {
|
|
||||||
try {
|
try {
|
||||||
const response = await getDevicesApi().getDeviceList();
|
const response = await devicesApi.getDeviceList();
|
||||||
this.setState({ devices: response.data.devices });
|
setDevices(response.data.devices);
|
||||||
console.log(response.data);
|
console.log(response.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleApiError(strings.errors.titleDeviceList, error);
|
handleApiError(strings.errors.titleDeviceList, error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
return (
|
||||||
|
<View style={pageStyle.container}>
|
||||||
|
<View style={pageItemStyle.container}>
|
||||||
|
<Text style={{ fontSize: 24, fontWeight: 'bold' }}>Devices</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pageItemStyle.container}>
|
||||||
|
<FlatList data={devices} renderItem={({ item }) => <DeviceItem device={item} onPress={onDevicePress} />} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeviceList;
|
||||||
|
|||||||
@@ -1,79 +1,36 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { clearSession } from '../store/SessionSlice';
|
||||||
import { pageStyle, pageItemStyle, primaryColor } from '../AppStyle';
|
import { pageStyle, pageItemStyle, primaryColor } from '../AppStyle';
|
||||||
import { View, Text, TextInput, Button, ActivityIndicator, Alert } from 'react-native';
|
import { View, Text, TextInput, Button, ActivityIndicator, Alert } from 'react-native';
|
||||||
import { strings } from '../localization/LocalizationStrings';
|
import { strings } from '../localization/LocalizationStrings';
|
||||||
import { authenticationApi, handleApiError } from '../api/apiHandler';
|
import { authenticationApi, handleApiError } from '../api/apiHandler';
|
||||||
import { useStore } from '../Store';
|
|
||||||
|
|
||||||
export default class ForgotPassword extends Component {
|
const ForgotPassword = props => {
|
||||||
state = {
|
const dispatch = useDispatch();
|
||||||
email: '',
|
const [email, setEmail] = useState();
|
||||||
loading: false,
|
const [loading, setLoading] = useState(false);
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
const validateEmail = () => {
|
||||||
return (
|
|
||||||
<View style={pageStyle.container}>
|
|
||||||
<View style={pageItemStyle.container}>
|
|
||||||
<Text>Forgot Password</Text>
|
|
||||||
</View>
|
|
||||||
<View style={pageItemStyle.container}>
|
|
||||||
<ActivityIndicator size="large" animating={this.state.loading} />
|
|
||||||
</View>
|
|
||||||
<View style={pageItemStyle.container}>
|
|
||||||
<TextInput
|
|
||||||
style={pageItemStyle.inputText}
|
|
||||||
placeholder={strings.placeholders.email}
|
|
||||||
autoComplete="email"
|
|
||||||
autoCapitalize="none"
|
|
||||||
autoFocus={true}
|
|
||||||
keyboardType="email-address"
|
|
||||||
textContentType="emailAddress"
|
|
||||||
returnKeyType="go"
|
|
||||||
onChangeText={text => this.setState({ email: text })}
|
|
||||||
onSubmitEditing={() => {
|
|
||||||
this.state.email && this.onSubmit;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<View style={pageItemStyle.containerButton}>
|
|
||||||
<Button
|
|
||||||
title={strings.buttons.sendEmail}
|
|
||||||
color={primaryColor()}
|
|
||||||
onPress={this.onSubmit}
|
|
||||||
disabled={this.state.loading || !this.state.email}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<View style={pageItemStyle.containerButton}>
|
|
||||||
<Button
|
|
||||||
title={strings.buttons.signIn}
|
|
||||||
color={primaryColor()}
|
|
||||||
onPress={this.backToSignin}
|
|
||||||
disabled={this.state.loading}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
validateEmail = () => {
|
|
||||||
const re = /\S+@\S+\.\S+/;
|
const re = /\S+@\S+\.\S+/;
|
||||||
const valid = re.test(this.state.email);
|
const valid = re.test(email);
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
Alert.alert(strings.errors.titleForgotPassword, strings.errors.badEmail);
|
Alert.alert(strings.errors.titleForgotPassword, strings.errors.badEmail);
|
||||||
}
|
}
|
||||||
return valid;
|
return valid;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const onSubmit = async () => {
|
||||||
|
if (validateEmail()) {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
onSubmit = async () => {
|
|
||||||
if (this.validateEmail()) {
|
|
||||||
this.setState({ loading: true });
|
|
||||||
try {
|
try {
|
||||||
useStore.getState().clearSession();
|
// Clear the session information
|
||||||
|
dispatch(clearSession());
|
||||||
|
|
||||||
const response = await authenticationApi.getAccessToken(
|
const response = await authenticationApi.getAccessToken(
|
||||||
{
|
{
|
||||||
userId: this.state.email,
|
userId: email,
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
true,
|
true,
|
||||||
@@ -82,12 +39,53 @@ export default class ForgotPassword extends Component {
|
|||||||
Alert.alert(strings.messages.message, strings.messages.resetEmail);
|
Alert.alert(strings.messages.message, strings.messages.resetEmail);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleApiError(strings.errors.titleForgotPassword, error);
|
handleApiError(strings.errors.titleForgotPassword, error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
this.setState({ loading: false });
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
backToSignin = () => {
|
const backToSignin = () => {
|
||||||
this.props.navigation.replace('SignIn');
|
props.navigation.replace('SignIn');
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
return (
|
||||||
|
<View style={pageStyle.container}>
|
||||||
|
<View style={pageItemStyle.container}>
|
||||||
|
<Text>Forgot Password</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pageItemStyle.container}>
|
||||||
|
<ActivityIndicator size="large" animating={loading} />
|
||||||
|
</View>
|
||||||
|
<View style={pageItemStyle.container}>
|
||||||
|
<TextInput
|
||||||
|
style={pageItemStyle.inputText}
|
||||||
|
placeholder={strings.placeholders.email}
|
||||||
|
autoComplete="email"
|
||||||
|
autoCapitalize="none"
|
||||||
|
autoFocus={true}
|
||||||
|
keyboardType="email-address"
|
||||||
|
textContentType="emailAddress"
|
||||||
|
returnKeyType="go"
|
||||||
|
onChangeText={text => setEmail(text)}
|
||||||
|
onSubmitEditing={() => {
|
||||||
|
email && onSubmit;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={pageItemStyle.containerButton}>
|
||||||
|
<Button
|
||||||
|
title={strings.buttons.sendEmail}
|
||||||
|
color={primaryColor}
|
||||||
|
onPress={onSubmit}
|
||||||
|
disabled={loading || !email}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={pageItemStyle.containerButton}>
|
||||||
|
<Button title={strings.buttons.signIn} color={primaryColor} onPress={backToSignin} disabled={loading} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ForgotPassword;
|
||||||
|
|||||||
@@ -1,23 +1,27 @@
|
|||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import { strings } from '../localization/LocalizationStrings';
|
import { strings } from '../localization/LocalizationStrings';
|
||||||
import { useStore } from '../Store';
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { clearSession } from '../store/SessionSlice';
|
||||||
import { pageStyle, pageItemStyle, primaryColor } from '../AppStyle';
|
import { pageStyle, pageItemStyle, primaryColor } from '../AppStyle';
|
||||||
import { View, Button } from 'react-native';
|
import { View, Button } from 'react-native';
|
||||||
|
|
||||||
export default class Profile extends Component {
|
const Profile = props => {
|
||||||
render() {
|
const dispatch = useDispatch();
|
||||||
return (
|
|
||||||
<View style={pageStyle.container}>
|
|
||||||
<View style={pageItemStyle.containerButton}>
|
|
||||||
<Button title={strings.buttons.signOut} color={primaryColor()} onPress={this.onSignOutPress} />
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
onSignOutPress = async () => {
|
const onSignOutPress = async () => {
|
||||||
// Clear the session information and go back to the sign in pageStyle
|
// Clear the session information and go back to the start
|
||||||
useStore.getState().clearSession();
|
dispatch(clearSession());
|
||||||
this.props.navigation.replace('BrandSelector');
|
|
||||||
|
props.navigation.replace('BrandSelector');
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
return (
|
||||||
|
<View style={pageStyle.container}>
|
||||||
|
<View style={pageItemStyle.containerButton}>
|
||||||
|
<Button title={strings.buttons.signOut} color={primaryColor} onPress={onSignOutPress} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Profile;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { pageStyle, pageItemStyle, primaryColor } from "../AppStyle";
|
import { pageStyle, pageItemStyle, primaryColor } from '../AppStyle';
|
||||||
import { View, Text, TextInput, Button, Alert, ActivityIndicator } from 'react-native';
|
import { View, Text, TextInput, Button, Alert, ActivityIndicator } from 'react-native';
|
||||||
import { strings } from '../localization/LocalizationStrings';
|
import { strings } from '../localization/LocalizationStrings';
|
||||||
import { authenticationApi, handleApiError } from '../api/apiHandler';
|
import { authenticationApi, handleApiError } from '../api/apiHandler';
|
||||||
@@ -13,7 +13,7 @@ export default function ResetPassword(props) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(userId, password);
|
console.log(userId, password);
|
||||||
}, []);
|
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
const onCancel = () => {
|
const onCancel = () => {
|
||||||
props.navigation.replace('SignIn');
|
props.navigation.replace('SignIn');
|
||||||
@@ -46,7 +46,7 @@ export default function ResetPassword(props) {
|
|||||||
const checkPassword = () => {
|
const checkPassword = () => {
|
||||||
const valid = validatePassword(newPassword);
|
const valid = validatePassword(newPassword);
|
||||||
|
|
||||||
if(newPassword === password) {
|
if (newPassword === password) {
|
||||||
Alert.alert(strings.errors.titleResetPassword, strings.errors.samePassword);
|
Alert.alert(strings.errors.titleResetPassword, strings.errors.samePassword);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -54,14 +54,14 @@ export default function ResetPassword(props) {
|
|||||||
Alert.alert(strings.errors.titleResetPassword, strings.errors.mismatchPassword);
|
Alert.alert(strings.errors.titleResetPassword, strings.errors.mismatchPassword);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if(!valid) {
|
if (!valid) {
|
||||||
Alert.alert(strings.errors.titleResetPassword, strings.errors.badFormat);
|
Alert.alert(strings.errors.titleResetPassword, strings.errors.badFormat);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return valid && newPassword !== password && newPassword === confirmPassword;
|
return valid && newPassword !== password && newPassword === confirmPassword;
|
||||||
};
|
};
|
||||||
|
|
||||||
const validatePassword = (password) => {
|
const validatePassword = password => {
|
||||||
const reg = /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/;
|
const reg = /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/;
|
||||||
return reg.test(password);
|
return reg.test(password);
|
||||||
};
|
};
|
||||||
@@ -102,17 +102,13 @@ export default function ResetPassword(props) {
|
|||||||
<View style={pageItemStyle.containerButton}>
|
<View style={pageItemStyle.containerButton}>
|
||||||
<Button
|
<Button
|
||||||
title={strings.buttons.submit}
|
title={strings.buttons.submit}
|
||||||
color={primaryColor()}
|
color={primaryColor}
|
||||||
onPress={onSubmit}
|
onPress={onSubmit}
|
||||||
disabled={loading || !newPassword || !confirmPassword}
|
disabled={loading || !newPassword || !confirmPassword}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View style={pageItemStyle.containerButton}>
|
<View style={pageItemStyle.containerButton}>
|
||||||
<Button
|
<Button title={strings.buttons.cancel} color={primaryColor} onPress={onCancel} disabled={loading} />
|
||||||
title={strings.buttons.cancel}
|
|
||||||
color={primaryColor()}
|
|
||||||
onPress={onCancel}
|
|
||||||
disabled={loading} />
|
|
||||||
</View>
|
</View>
|
||||||
<View style={pageItemStyle.container}>
|
<View style={pageItemStyle.container}>
|
||||||
<View>
|
<View>
|
||||||
|
|||||||
@@ -1,123 +1,60 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { useState, useEffect, createRef } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { clearSession, setSession } from '../store/SessionSlice';
|
||||||
|
import { selectBrandInfo } from '../store/BrandInfoSlice';
|
||||||
import { strings } from '../localization/LocalizationStrings';
|
import { strings } from '../localization/LocalizationStrings';
|
||||||
import { useStore } from '../Store';
|
|
||||||
import { pageStyle, pageItemStyle, primaryColor, primaryColorStyle } from '../AppStyle';
|
import { pageStyle, pageItemStyle, primaryColor, primaryColorStyle } from '../AppStyle';
|
||||||
import { StyleSheet, Text, View, Image, Button, TextInput, ActivityIndicator } from 'react-native';
|
import { StyleSheet, Text, View, Image, Button, TextInput, ActivityIndicator } from 'react-native';
|
||||||
import { handleApiError, authenticationApi, setApiSystemInfo } from '../api/apiHandler';
|
import { handleApiError, authenticationApi, setApiSystemInfo } from '../api/apiHandler';
|
||||||
|
|
||||||
export default class SignIn extends Component {
|
const SignIn = props => {
|
||||||
state = {
|
const dispatch = useDispatch();
|
||||||
email: '',
|
const brandInfo = useSelector(selectBrandInfo);
|
||||||
password: '',
|
const [email, setEmail] = useState();
|
||||||
loading: false,
|
const [password, setPassword] = useState();
|
||||||
};
|
const [loading, setLoading] = useState(false);
|
||||||
|
const passwordRef = createRef();
|
||||||
|
|
||||||
constructor(props) {
|
useEffect(() => {
|
||||||
super(props);
|
|
||||||
this.passwordRef = React.createRef();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
// If the brand is not selected, then resort back to the brand selector
|
// If the brand is not selected, then resort back to the brand selector
|
||||||
if (useStore.getState().brandInfo === null) {
|
if (brandInfo === null) {
|
||||||
this.props.navigation.replace('BrandSelector');
|
props.navigation.replace('BrandSelector');
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
render() {
|
const onSignInPress = async () => {
|
||||||
return (
|
|
||||||
<View style={pageStyle.container}>
|
|
||||||
<View style={pageItemStyle.container}>
|
|
||||||
<Image style={signInStyle.headerImage} source={{ uri: useStore.getState().brandInfo.iconUri }} />
|
|
||||||
</View>
|
|
||||||
{this.state.loading ? (
|
|
||||||
<View style={pageItemStyle.container}>
|
|
||||||
<ActivityIndicator size="large" color={primaryColor()} animating={this.state.loading} />
|
|
||||||
</View>
|
|
||||||
) : (
|
|
||||||
<View style={signInStyle.containerForm}>
|
|
||||||
<View style={pageItemStyle.container}>
|
|
||||||
<Text style={pageItemStyle.description}>{strings.signIn.description}</Text>
|
|
||||||
</View>
|
|
||||||
<View style={pageItemStyle.container}>
|
|
||||||
<TextInput
|
|
||||||
style={pageItemStyle.inputText}
|
|
||||||
placeholder={strings.placeholders.username}
|
|
||||||
autoComplete="email"
|
|
||||||
autoCapitalize="none"
|
|
||||||
autoFocus={true}
|
|
||||||
keyboardType="email-address"
|
|
||||||
textContentType="username"
|
|
||||||
returnKeyType="next"
|
|
||||||
value={this.state.email}
|
|
||||||
onChangeText={text => this.setState({ email: text })}
|
|
||||||
onSubmitEditing={() => this.passwordRef.current.focus()}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<View style={pageItemStyle.container}>
|
|
||||||
<TextInput
|
|
||||||
style={pageItemStyle.inputText}
|
|
||||||
ref={this.passwordRef}
|
|
||||||
placeholder={strings.placeholders.password}
|
|
||||||
secureTextEntry={true}
|
|
||||||
autoCapitalize="none"
|
|
||||||
textContentType="password"
|
|
||||||
returnKeyType="go"
|
|
||||||
onChangeText={text => this.setState({ password: text })}
|
|
||||||
onSubmitEditing={() => this.onSignInPress()}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<View style={pageItemStyle.containerButton}>
|
|
||||||
<Text style={[pageItemStyle.buttonText, primaryColorStyle()]} onPress={this.onForgotPasswordPress}>
|
|
||||||
{strings.buttons.forgotPassword}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<View style={pageItemStyle.containerButton}>
|
|
||||||
<Button title={strings.buttons.signIn} color={primaryColor()} onPress={this.onSignInPress} />
|
|
||||||
</View>
|
|
||||||
{/*<View style={pageItemStyle.containerButton}>
|
|
||||||
<Button title={"Reset"}
|
|
||||||
onPress={() => this.props.navigation.navigate("ResetPassword", {userId: this.state.email, password: this.state.password})} />
|
|
||||||
</View>*/}
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
onSignInPress = async () => {
|
|
||||||
try {
|
try {
|
||||||
this.setState({ loading: true });
|
setLoading(true);
|
||||||
|
|
||||||
// Make sure to clear any session information, this ensures error messaging is handled properly as well
|
// Make sure to clear any session information, this ensures error messaging is handled properly as well
|
||||||
useStore.getState().clearSession();
|
dispatch(clearSession());
|
||||||
|
|
||||||
const response = await authenticationApi.getAccessToken({
|
const response = await authenticationApi.getAccessToken({
|
||||||
userId: this.state.email,
|
userId: email,
|
||||||
password: this.state.password,
|
password: password,
|
||||||
});
|
});
|
||||||
useStore.getState().setSession(response.data);
|
dispatch(setSession(response.data));
|
||||||
|
|
||||||
// must reset password
|
// must reset password
|
||||||
console.log(JSON.stringify(response.data, null, '\t'));
|
console.log(JSON.stringify(response.data, null, '\t'));
|
||||||
if (response.data.userMustChangePassword) {
|
if (response.data.userMustChangePassword) {
|
||||||
this.props.navigation.navigate('ResetPassword', {
|
props.navigation.navigate('ResetPassword', {
|
||||||
userId: this.state.email,
|
userId: email,
|
||||||
password: this.state.password,
|
password: password,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Update the system endpoints and navigate to the main view.
|
// Update the system endpoints and navigate to the main view.
|
||||||
this.getSystemEndpointsNavigateToMain();
|
getSystemEndpointsNavigateToMain();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Clear the loading state
|
// Clear the loading state
|
||||||
this.setState({ loading: false });
|
setLoading(false);
|
||||||
|
|
||||||
handleApiError(strings.errors.titleSignIn, error);
|
handleApiError(strings.errors.titleSignIn, error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
getSystemEndpointsNavigateToMain = async () => {
|
const getSystemEndpointsNavigateToMain = async () => {
|
||||||
try {
|
try {
|
||||||
// The system info is necessary before moving on to the next view as it'll provide
|
// The system info is necessary before moving on to the next view as it'll provide
|
||||||
// the endpoints needed for communicating with the other systems
|
// the endpoints needed for communicating with the other systems
|
||||||
@@ -129,19 +66,74 @@ export default class SignIn extends Component {
|
|||||||
setApiSystemInfo(response.data);
|
setApiSystemInfo(response.data);
|
||||||
|
|
||||||
// Replace to the main screen. Use replace to ensure no back button
|
// Replace to the main screen. Use replace to ensure no back button
|
||||||
this.props.navigation.replace('Main');
|
props.navigation.replace('Main');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Make sure the loading state is done in all cases
|
// Make sure the loading state is done in all cases
|
||||||
this.setState({ loading: false });
|
setLoading(false);
|
||||||
|
|
||||||
handleApiError(strings.errors.titleSystemSetup, error);
|
handleApiError(strings.errors.titleSystemSetup, error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onForgotPasswordPress = async () => {
|
const onForgotPasswordPress = async () => {
|
||||||
this.props.navigation.navigate('ForgotPassword');
|
props.navigation.navigate('ForgotPassword');
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
return (
|
||||||
|
<View style={pageStyle.container}>
|
||||||
|
<View style={pageItemStyle.container}>
|
||||||
|
<Image style={signInStyle.headerImage} source={{ uri: brandInfo.iconUri }} />
|
||||||
|
</View>
|
||||||
|
{loading ? (
|
||||||
|
<View style={pageItemStyle.container}>
|
||||||
|
<ActivityIndicator size="large" color={primaryColor} animating={loading} />
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<View style={signInStyle.containerForm}>
|
||||||
|
<View style={pageItemStyle.container}>
|
||||||
|
<Text style={pageItemStyle.description}>{strings.signIn.description}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pageItemStyle.container}>
|
||||||
|
<TextInput
|
||||||
|
style={pageItemStyle.inputText}
|
||||||
|
placeholder={strings.placeholders.username}
|
||||||
|
autoComplete="email"
|
||||||
|
autoCapitalize="none"
|
||||||
|
autoFocus={true}
|
||||||
|
keyboardType="email-address"
|
||||||
|
textContentType="username"
|
||||||
|
returnKeyType="next"
|
||||||
|
value={email}
|
||||||
|
onChangeText={text => setEmail(text)}
|
||||||
|
onSubmitEditing={() => passwordRef.current.focus()}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={pageItemStyle.container}>
|
||||||
|
<TextInput
|
||||||
|
style={pageItemStyle.inputText}
|
||||||
|
ref={passwordRef}
|
||||||
|
placeholder={strings.placeholders.password}
|
||||||
|
secureTextEntry={true}
|
||||||
|
autoCapitalize="none"
|
||||||
|
textContentType="password"
|
||||||
|
returnKeyType="go"
|
||||||
|
onChangeText={text => setPassword(text)}
|
||||||
|
onSubmitEditing={() => onSignInPress()}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={pageItemStyle.containerButton}>
|
||||||
|
<Text style={[pageItemStyle.buttonText, primaryColorStyle]} onPress={onForgotPasswordPress}>
|
||||||
|
{strings.buttons.forgotPassword}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pageItemStyle.containerButton}>
|
||||||
|
<Button title={strings.buttons.signIn} color={primaryColor} onPress={onSignInPress} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const signInStyle = StyleSheet.create({
|
const signInStyle = StyleSheet.create({
|
||||||
containerForm: {
|
containerForm: {
|
||||||
@@ -160,3 +152,5 @@ const signInStyle = StyleSheet.create({
|
|||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export default SignIn;
|
||||||
|
|||||||
20
src/store/BrandInfoSlice.js
Normal file
20
src/store/BrandInfoSlice.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
export const brandInfoSlice = createSlice({
|
||||||
|
name: 'brandInfo',
|
||||||
|
initialState: {
|
||||||
|
value: null,
|
||||||
|
},
|
||||||
|
reducers: {
|
||||||
|
setBrandInfo: (state, action) => {
|
||||||
|
state.value = action.payload;
|
||||||
|
},
|
||||||
|
clearBrandInfo: state => {
|
||||||
|
state.value -= null;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const selectBrandInfo = state => state.brandInfo.value;
|
||||||
|
export const { setBrandInfo, clearBrandInfo } = brandInfoSlice.actions;
|
||||||
|
export default brandInfoSlice.reducer;
|
||||||
20
src/store/SessionSlice.js
Normal file
20
src/store/SessionSlice.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
export const sessionSlice = createSlice({
|
||||||
|
name: 'session',
|
||||||
|
initialState: {
|
||||||
|
value: null,
|
||||||
|
},
|
||||||
|
reducers: {
|
||||||
|
setSession: (state, action) => {
|
||||||
|
state.value = action.payload;
|
||||||
|
},
|
||||||
|
clearSession: state => {
|
||||||
|
state.value -= null;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const selectSession = state => state.session.value;
|
||||||
|
export const { setSession, clearSession } = sessionSlice.actions;
|
||||||
|
export default sessionSlice.reducer;
|
||||||
16
src/store/Store.js
Normal file
16
src/store/Store.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { configureStore } from '@reduxjs/toolkit';
|
||||||
|
import brandInfoReducer from './BrandInfoSlice';
|
||||||
|
import sessionReducer from './SessionSlice';
|
||||||
|
import systemInfoReducer from './SystemInfoSlice';
|
||||||
|
|
||||||
|
export default configureStore({
|
||||||
|
reducer: {
|
||||||
|
brandInfo: brandInfoReducer,
|
||||||
|
session: sessionReducer,
|
||||||
|
systemInfo: systemInfoReducer,
|
||||||
|
},
|
||||||
|
middleware: getDefaultMiddleware =>
|
||||||
|
getDefaultMiddleware({
|
||||||
|
serializableCheck: false,
|
||||||
|
}),
|
||||||
|
});
|
||||||
20
src/store/SystemInfoSlice.js
Normal file
20
src/store/SystemInfoSlice.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
export const systemInfoSlice = createSlice({
|
||||||
|
name: 'systemInfo',
|
||||||
|
initialState: {
|
||||||
|
value: null,
|
||||||
|
},
|
||||||
|
reducers: {
|
||||||
|
setSystemInfo: (state, action) => {
|
||||||
|
state.value = action.payload;
|
||||||
|
},
|
||||||
|
clearSystemInfo: state => {
|
||||||
|
state.value -= null;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const selectSystemInfo = state => state.systemInfo.value;
|
||||||
|
export const { setSystemInfo, clearSystemInfo } = systemInfoSlice.actions;
|
||||||
|
export default systemInfoSlice.reducer;
|
||||||
Reference in New Issue
Block a user