Revert "feat: add transaction history"

This commit is contained in:
Paolo Miguel de Leon
2022-11-08 11:19:44 +08:00
committed by GitHub
parent 09d829a3ee
commit 3953be2b9e
22 changed files with 262 additions and 743 deletions

View File

@@ -37,7 +37,6 @@
"@typescript-eslint/no-unused-vars": [ "@typescript-eslint/no-unused-vars": [
"error", "error",
{ "ignoreRestSiblings": true } { "ignoreRestSiblings": true }
], ]
"react/prop-types": ["warn"]
} }
} }

View File

@@ -1,33 +0,0 @@
import React, { useContext } from 'react';
import { useSelector } from '@xstate/react';
import { getVersion } from 'react-native-device-info';
import { selectBackendInfo } from '../machines/app';
import { GlobalContext } from '../shared/GlobalContext';
import { Column, Text } from './ui';
import { Colors } from './ui/styleUtils';
export const AppVersion: React.FC = () => {
const { appService } = useContext(GlobalContext);
const backendInfo = useSelector(appService, selectBackendInfo);
return (
<Column>
<VersionText>Version: {getVersion()}</VersionText>
{backendInfo.application.name !== '' ? (
<React.Fragment>
<VersionText>
{backendInfo.application.name}: {backendInfo.application.version}
</VersionText>
<VersionText>MOSIP: {backendInfo.config['mosip.host']}</VersionText>
</React.Fragment>
) : null}
</Column>
);
};
const VersionText: React.FC = (props) => (
<Text weight="semibold" align="center" size="smaller" color={Colors.Grey}>
{props.children}
</Text>
);

View File

@@ -1,74 +0,0 @@
import React from 'react';
import { StyleSheet } from 'react-native';
import {
MOSIPServiceHistoryItem,
MOSIPServiceHistoryItemEventStatus,
} from '../screens/Profile/TransactionHistoryScreenController';
import { Column, Row, Text } from './ui';
import { Colors } from './ui/styleUtils';
export const TransactionHistoryItem: React.FC<TransactionHistoryItemProps> = (
props
) => {
const { data } = props;
return (
<Column style={styles.container}>
<Row margin={[0, 0, 8, 0]}>
<Column fill margin={[0, 8, 0, 0]}>
<Text size="small" weight="semibold" numLines={1}>
{data.eventId}
</Text>
<Text size="smaller" color={Colors.Grey}>
{new Date(data.timeStamp).toLocaleString()}
</Text>
</Column>
<Column crossAlign="flex-end">
<Text
size="smaller"
weight="semibold"
style={[statusStyles.base, statusStyles[data.eventStatus]]}>
{data.eventStatus}
</Text>
</Column>
</Row>
<Text size="small">{data.description.trim()}</Text>
</Column>
);
};
interface TransactionHistoryItemProps {
data: MOSIPServiceHistoryItem;
}
const styles = StyleSheet.create({
container: {
backgroundColor: Colors.White,
marginBottom: 8,
paddingHorizontal: 16,
paddingVertical: 8,
},
});
const statusStyles = StyleSheet.create({
base: {
borderRadius: 100,
color: Colors.White,
backgroundColor: Colors.Grey,
paddingHorizontal: 8,
paddingVertical: 2,
},
[MOSIPServiceHistoryItemEventStatus.Failed]: {
backgroundColor: Colors.Red,
},
[MOSIPServiceHistoryItemEventStatus.InProgress]: {
backgroundColor: Colors.Orange,
},
[MOSIPServiceHistoryItemEventStatus.Success]: {
backgroundColor: Colors.Green,
},
});

View File

@@ -69,13 +69,13 @@ PODS:
- GoogleNetworkingUtilities (~> 1.2) - GoogleNetworkingUtilities (~> 1.2)
- GoogleSymbolUtilities (~> 1.1) - GoogleSymbolUtilities (~> 1.1)
- GoogleUtilitiesLegacy (~> 1.3) - GoogleUtilitiesLegacy (~> 1.3)
- Permission-BluetoothPeripheral (3.6.1): - Permission-BluetoothPeripheral (3.6.0):
- RNPermissions - RNPermissions
- Permission-Camera (3.6.1): - Permission-Camera (3.6.0):
- RNPermissions - RNPermissions
- Permission-LocationAccuracy (3.6.1): - Permission-LocationAccuracy (3.6.0):
- RNPermissions - RNPermissions
- Permission-LocationWhenInUse (3.6.1): - Permission-LocationWhenInUse (3.6.0):
- RNPermissions - RNPermissions
- RCT-Folly (2020.01.13.00): - RCT-Folly (2020.01.13.00):
- boost-for-react-native - boost-for-react-native
@@ -340,24 +340,24 @@ PODS:
- React-cxxreact (= 0.64.4) - React-cxxreact (= 0.64.4)
- React-jsi (= 0.64.4) - React-jsi (= 0.64.4)
- React-perflogger (= 0.64.4) - React-perflogger (= 0.64.4)
- RNBluetoothStateManager (1.3.4): - RNBluetoothStateManager (1.3.3):
- React-Core - React-Core
- RNCAsyncStorage (1.15.17): - RNCAsyncStorage (1.15.17):
- React-Core - React-Core
- RNCPicker (2.2.1): - RNCPicker (2.2.1):
- React-Core - React-Core
- RNDeviceInfo (8.7.1): - RNDeviceInfo (8.7.0):
- React-Core - React-Core
- RNGestureHandler (2.1.3): - RNGestureHandler (2.1.3):
- React-Core - React-Core
- RNKeychain (8.0.0): - RNKeychain (8.0.0):
- React-Core - React-Core
- RNPermissions (3.6.1): - RNPermissions (3.6.0):
- React-Core - React-Core
- RNScreens (3.10.2): - RNScreens (3.10.2):
- React-Core - React-Core
- React-RCTImage - React-RCTImage
- RNSecureRandom (1.0.1): - RNSecureRandom (1.0.0):
- React - React
- RNSVG (12.1.1): - RNSVG (12.1.1):
- React - React
@@ -613,7 +613,7 @@ SPEC CHECKSUMS:
EXUpdates: a83e036243b0f6ece53a8c1cb883b6751c88a5f8 EXUpdates: a83e036243b0f6ece53a8c1cb883b6751c88a5f8
EXUpdatesInterface: a9814f422d3cd6e7cfd260d13c27786148ece20e EXUpdatesInterface: a9814f422d3cd6e7cfd260d13c27786148ece20e
FBLazyVector: fa8275d5086566e22a26ddc385ab5772e7f9b1bd FBLazyVector: fa8275d5086566e22a26ddc385ab5772e7f9b1bd
FBReactNativeSpec: c3dafd68550f3c95f009beee5c20ab07949ec4e4 FBReactNativeSpec: 7ead992e0bbaf608b93d456361caa6ccf6745df5
glog: 73c2498ac6884b13ede40eda8228cb1eee9d9d62 glog: 73c2498ac6884b13ede40eda8228cb1eee9d9d62
GoogleInterchangeUtilities: d5bc4d88d5b661ab72f9d70c58d02ca8c27ad1f7 GoogleInterchangeUtilities: d5bc4d88d5b661ab72f9d70c58d02ca8c27ad1f7
GoogleNetworkingUtilities: 3edd3a8161347494f2da60ea0deddc8a472d94cb GoogleNetworkingUtilities: 3edd3a8161347494f2da60ea0deddc8a472d94cb
@@ -621,10 +621,10 @@ SPEC CHECKSUMS:
GoogleUtilitiesLegacy: 5501bedec1646bd284286eb5fc9453f7e23a12f4 GoogleUtilitiesLegacy: 5501bedec1646bd284286eb5fc9453f7e23a12f4
mosip-inji-face-sdk: f0e765373b50324243d904e45eb3ce899db951ac mosip-inji-face-sdk: f0e765373b50324243d904e45eb3ce899db951ac
NearbyMessages: bd9e88f2df7fbab78be58fed58580d5d5bd62cbf NearbyMessages: bd9e88f2df7fbab78be58fed58580d5d5bd62cbf
Permission-BluetoothPeripheral: 67708853584bb9208c76d36d0e0ea4eafb97ea5b Permission-BluetoothPeripheral: 2a5154a9dfdb1cfcf1d546650ced9671904a02af
Permission-Camera: bf6791b17c7f614b6826019fcfdcc286d3a107f6 Permission-Camera: 0a0fb4341f50ab242f496fb2f73380e0ec454fe7
Permission-LocationAccuracy: 76df17de5c6b8bc2eee34e61ee92cdd7a864c73d Permission-LocationAccuracy: 13cbce13607e0738f1339447b4c5f51aa2c2b597
Permission-LocationWhenInUse: 3ba99e45c852763f730eabecec2870c2382b7bd4 Permission-LocationWhenInUse: 51aa065819cd582517e98e89b564e2465a4a83c6
RCT-Folly: ec7a233ccc97cc556cf7237f0db1ff65b986f27c RCT-Folly: ec7a233ccc97cc556cf7237f0db1ff65b986f27c
RCTRequired: f85fa00af016059cf88b90b8f8ff9a6af9e4b6c3 RCTRequired: f85fa00af016059cf88b90b8f8ff9a6af9e4b6c3
RCTTypeSafety: 5279aaf0fb1ad715cbbbbee32d5c98c72598bc9c RCTTypeSafety: 5279aaf0fb1ad715cbbbbee32d5c98c72598bc9c
@@ -651,15 +651,15 @@ SPEC CHECKSUMS:
React-RCTVibration: 761849eea2a1abc99d5e4171bae17ab3da3143ac React-RCTVibration: 761849eea2a1abc99d5e4171bae17ab3da3143ac
React-runtimeexecutor: 5b441857030bb6c3abaa7517f333cb01875ae499 React-runtimeexecutor: 5b441857030bb6c3abaa7517f333cb01875ae499
ReactCommon: b4a65d2d6e9eeffd4b32dde1245962b3f43907d0 ReactCommon: b4a65d2d6e9eeffd4b32dde1245962b3f43907d0
RNBluetoothStateManager: ae6a26260cbdf1827b58bd3bcc563527d61e6488 RNBluetoothStateManager: 4f2d73aecf081b97024116cba628e36c5b283791
RNCAsyncStorage: 6bd5a7ba3dde1c3facba418aa273f449bdc5437a RNCAsyncStorage: 6bd5a7ba3dde1c3facba418aa273f449bdc5437a
RNCPicker: cb57c823d5ce8d2d0b5dfb45ad97b737260dc59e RNCPicker: cb57c823d5ce8d2d0b5dfb45ad97b737260dc59e
RNDeviceInfo: aad3c663b25752a52bf8fce93f2354001dd185aa RNDeviceInfo: 36286df381fcaf1933ff9d2d3c34ba2abeb2d8d8
RNGestureHandler: e1099204721a17a89c81fcd1cc2e92143dc040fb RNGestureHandler: e1099204721a17a89c81fcd1cc2e92143dc040fb
RNKeychain: 4f63aada75ebafd26f4bc2c670199461eab85d94 RNKeychain: 4f63aada75ebafd26f4bc2c670199461eab85d94
RNPermissions: dcdb7b99796bbeda6975a6e79ad519c41b251b1c RNPermissions: de7b7c3fe1680d974ac7a85e3e97aa539c0e68ea
RNScreens: d6da2b9e29cf523832c2542f47bf1287318b1868 RNScreens: d6da2b9e29cf523832c2542f47bf1287318b1868
RNSecureRandom: 07efbdf2cd99efe13497433668e54acd7df49fef RNSecureRandom: 0dcee021fdb3d50cd5cee5db0ebf583c42f5af0e
RNSVG: 551acb6562324b1d52a4e0758f7ca0ec234e278f RNSVG: 551acb6562324b1d52a4e0758f7ca0ec234e278f
RNVectorIcons: 31cebfcf94e8cf8686eb5303ae0357da64d7a5a4 RNVectorIcons: 31cebfcf94e8cf8686eb5303ae0357da64d7a5a4
smartshare-react-native: 133dca4c48dea0908649c680701f0948317378c5 smartshare-react-native: 133dca4c48dea0908649c680701f0948317378c5

View File

@@ -132,30 +132,24 @@
"confirmPasscode": "Confirm your passcode", "confirmPasscode": "Confirm your passcode",
"enterPasscode": "Enter your passcode" "enterPasscode": "Enter your passcode"
}, },
"ProfileLayout": { "Credits": {
"profile": "Profile", "header": "Credits and legal notices",
"creditsScreen": { "back": "Back"
"title": "Credits and legal notices" },
}, "ProfileScreen": {
"revokeScreen": {
"title": "Revoke VID",
"revokingVids": "You are about to revoke ({{count}}) VIDs.",
"revokingVidsAfter": "This means you will no longer be able to use or view any of the IDs linked to those VID(s). \nAre you sure you want to proceed?",
"empty": "Empty",
"revokeSuccessful": "VID successfully revoked"
},
"transactionHistoryScreen": {
"title": "Transaction History",
"showingRecords": "Showing last {{numRecords}} records",
"noRecords": "No records found",
"fetchingRecords": "Fetching records..."
},
"name": "Name", "name": "Name",
"vcLabel": "VC Label", "vcLabel": "VC Label",
"language": "Language", "language": "Language",
"bioUnlock": "Biometric unlock", "bioUnlock": "Biometric unlock",
"authFactorUnlock": "Unlock auth factor", "authFactorUnlock": "Unlock auth factor",
"logout": "Log-out" "credits": "Credits and legal notices",
"logout": "Log-out",
"revokeLabel": "Revoke VID",
"revokeHeader": "REVOKE VID",
"revokingVids": "You are about to revoke ({{count}}) VIDs.",
"revokingVidsAfter": "This means you will no longer be able to use or view any of the IDs linked to those VID(s). \nAre you sure you want to proceed?",
"empty": "Empty",
"revokeSuccessful": "VID successfully revoked"
}, },
"ReceiveVcScreen": { "ReceiveVcScreen": {
"header": "{{vcLabel}} details", "header": "{{vcLabel}} details",

View File

@@ -100,4 +100,4 @@
"react": "17.0.1", "react": "17.0.1",
"react-native": "0.64.4" "react-native": "0.64.4"
} }
} }

View File

@@ -3,12 +3,11 @@ import {
BottomTabNavigationOptions, BottomTabNavigationOptions,
BottomTabScreenProps, BottomTabScreenProps,
} from '@react-navigation/bottom-tabs'; } from '@react-navigation/bottom-tabs';
import { HomeScreen } from '../screens/Home/HomeScreen'; import { HomeScreen } from '../screens/Home/HomeScreen';
import { ProfileScreen } from '../screens/Profile/ProfileScreen';
import { RootStackParamList } from './index'; import { RootStackParamList } from './index';
import { RequestLayout } from '../screens/Request/RequestLayout'; import { RequestLayout } from '../screens/Request/RequestLayout';
import { ScanLayout } from '../screens/Scan/ScanLayout'; import { ScanLayout } from '../screens/Scan/ScanLayout';
import { ProfileLayout } from '../screens/Profile/ProfileLayout';
export const mainRoutes: TabScreen[] = [ export const mainRoutes: TabScreen[] = [
{ {
@@ -34,11 +33,8 @@ export const mainRoutes: TabScreen[] = [
}, },
{ {
name: 'Profile', name: 'Profile',
component: ProfileLayout, component: ProfileScreen,
icon: 'person', icon: 'person',
options: {
headerShown: false,
},
}, },
]; ];

View File

@@ -0,0 +1,4 @@
{
"header": "Credits and legal notices",
"back": "Back"
}

101
screens/Profile/Credits.tsx Normal file
View File

@@ -0,0 +1,101 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
Dimensions,
Image,
SafeAreaView,
StyleSheet,
View,
} from 'react-native';
import { Divider, Icon, ListItem, Overlay } from 'react-native-elements';
import Markdown from 'react-native-simple-markdown';
import { Button, Text, Row } from '../../components/ui';
import { Colors } from '../../components/ui/styleUtils';
import creditsContent from '../../Credits.md';
export const Credits: React.FC<CreditsProps> = (props) => {
const { t } = useTranslation('Credits');
const [isViewing, setIsViewing] = useState(false);
const images = {
'docs/images/newlogic_logo.png': require('../../docs/images/newlogic_logo.png'),
'docs/images/id_pass_logo.png': require('../../docs/images/id_pass_logo.png'),
};
const styles = StyleSheet.create({
buttonContainer: {
position: 'absolute',
left: 0,
right: 'auto',
},
view: {
flex: 1,
width: Dimensions.get('screen').width,
},
markdownView: {
padding: 20,
},
});
const markdownStyles = {
heading1: {
fontSize: 24,
},
image: {
maxWidth: 150,
margin: 0,
},
};
const rules = {
image: {
react: (node, output, state) => (
<View key={`image-${state.key}`}>
<Image
style={{ maxWidth: 150, height: 100 }}
source={images[node.target]}
resizeMode="contain"
/>
</View>
),
},
};
return (
<ListItem bottomDivider onPress={() => setIsViewing(true)}>
<ListItem.Content>
<ListItem.Title>
<Text>{props.label}</Text>
</ListItem.Title>
</ListItem.Content>
<Overlay
overlayStyle={{ padding: 24 }}
isVisible={isViewing}
onBackdropPress={() => setIsViewing(false)}>
<SafeAreaView>
<View style={styles.view}>
<Row align="center" crossAlign="center" margin="0 0 10 0">
<View style={styles.buttonContainer}>
<Button
type="clear"
icon={<Icon name="chevron-left" color={Colors.Orange} />}
title=""
onPress={() => setIsViewing(false)}
/>
</View>
<Text size="small">{t('header')}</Text>
</Row>
<Divider />
<View style={styles.markdownView}>
<Markdown rules={rules} styles={markdownStyles}>
{creditsContent}
</Markdown>
</View>
</View>
</SafeAreaView>
</Overlay>
</ListItem>
);
};
interface CreditsProps {
label: string;
}

View File

@@ -1,56 +0,0 @@
import React from 'react';
import { Image, View } from 'react-native';
import Markdown from 'react-native-simple-markdown';
import { Column } from '../../components/ui';
import { Colors } from '../../components/ui/styleUtils';
import creditsContent from '../../Credits.md';
export const CreditsScreen: React.FC<CreditsProps> = () => {
const images = {
'docs/images/newlogic_logo.png': require('../../docs/images/newlogic_logo.png'),
'docs/images/id_pass_logo.png': require('../../docs/images/id_pass_logo.png'),
};
const markdownStyles = {
text: {
color: Colors.Black,
fontFamily: 'Poppins_400Regular',
},
heading: {
fontFamily: 'Poppins_600SemiBold',
fontWeight: '600',
},
image: {
maxWidth: 150,
margin: 0,
},
};
const rules = {
image: {
react: (node, output, state) => (
<View key={`image-${state.key}`}>
<Image
style={{ maxWidth: 150, height: 100 }}
source={images[node.target]}
resizeMode="contain"
/>
</View>
),
},
};
return (
<Column fill safe backgroundColor={Colors.White} padding="20">
<Markdown rules={rules} styles={markdownStyles}>
{creditsContent}
</Markdown>
</Column>
);
};
interface CreditsProps {
label: string;
}

View File

@@ -1,25 +0,0 @@
{
"profile": "Profile",
"creditsScreen": {
"title": "Credits and legal notices"
},
"revokeScreen": {
"title": "Revoke VID",
"revokingVids": "You are about to revoke ({{count}}) VIDs.",
"revokingVidsAfter": "This means you will no longer be able to use or view any of the IDs linked to those VID(s). \nAre you sure you want to proceed?",
"empty": "Empty",
"revokeSuccessful": "VID successfully revoked"
},
"transactionHistoryScreen": {
"title": "Transaction History",
"showingRecords": "Showing last {{numRecords}} records",
"noRecords": "No records found",
"fetchingRecords": "Fetching records..."
},
"name": "Name",
"vcLabel": "VC Label",
"language": "Language",
"bioUnlock": "Biometric unlock",
"authFactorUnlock": "Unlock auth factor",
"logout": "Log-out"
}

View File

@@ -1,83 +0,0 @@
import React from 'react';
import {
createNativeStackNavigator,
NativeStackNavigationOptions,
NativeStackScreenProps,
} from '@react-navigation/native-stack';
import { ProfileScreen } from './ProfileScreen';
import { TransactionHistoryScreen } from './TransactionHistoryScreen';
import { NavigationProp } from '@react-navigation/native';
import { MainBottomTabParamList } from '../../routes/main';
import { CreditsScreen } from './CreditsScreen';
import { RevokeScreen } from './RevokeScreen';
import { Icon } from 'react-native-elements';
import { Colors } from '../../components/ui/styleUtils';
import { Row } from '../../components/ui';
import { LanguageSelector } from '../../components/LanguageSelector';
import { useTranslation } from 'react-i18next';
const ProfileStack = createNativeStackNavigator();
export type ProfileStackParamList = {
RevokeScreen: undefined;
ProfileScreen: undefined;
TransactionHistoryScreen: undefined;
CreditsScreen: undefined;
};
export type ScanLayoutNavigation = NavigationProp<
ProfileStackParamList & MainBottomTabParamList
>;
export const ProfileLayout: React.FC<
NativeStackScreenProps<MainBottomTabParamList, 'Profile'>
> = (props) => {
const { t } = useTranslation('ProfileLayout');
const options: NativeStackNavigationOptions = {
headerLeft: () => (
<Row margin={[0, 16, 0, 0]}>
<Icon
name="chevron-left"
color={Colors.Orange}
onPress={() => props.navigation.pop()}
/>
</Row>
),
headerRight: () => (
<LanguageSelector
triggerComponent={<Icon name="language" color={Colors.Orange} />}
/>
),
headerTitleAlign: 'center',
title: t('profile').toUpperCase(),
};
return (
<ProfileStack.Navigator
initialRouteName="ProfileScreen"
screenOptions={options}>
<ProfileStack.Screen name="ProfileScreen" component={ProfileScreen} />
<ProfileStack.Screen
name="TransactionHistoryScreen"
component={TransactionHistoryScreen}
options={{
title: t('transactionHistoryScreen.title'),
}}
/>
<ProfileStack.Screen
name="RevokeScreen"
component={RevokeScreen}
options={{
title: t('revokeScreen.title'),
}}
/>
<ProfileStack.Screen
name="CreditsScreen"
component={CreditsScreen}
options={{
title: t('creditsScreen.title'),
}}
/>
</ProfileStack.Navigator>
);
};

View File

@@ -0,0 +1,15 @@
{
"name": "Name",
"vcLabel": "VC Label",
"language": "Language",
"bioUnlock": "Biometric unlock",
"authFactorUnlock": "Unlock auth factor",
"credits": "Credits and legal notices",
"logout": "Log-out",
"revokeLabel": "Revoke VID",
"revokeHeader": "REVOKE VID",
"revokingVids": "You are about to revoke ({{count}}) VIDs.",
"revokingVidsAfter": "This means you will no longer be able to use or view any of the IDs linked to those VID(s). \nAre you sure you want to proceed?",
"empty": "Empty",
"revokeSuccessful": "VID successfully revoked"
}

View File

@@ -1,110 +1,21 @@
import React from 'react'; import React from 'react';
import { View } from 'react-native';
import { getVersion } from 'react-native-device-info';
import { ListItem, Switch } from 'react-native-elements'; import { ListItem, Switch } from 'react-native-elements';
import { useTranslation } from 'react-i18next';
import i18next, { SUPPORTED_LANGUAGES } from '../../i18n';
import { Column, Text } from '../../components/ui'; import { Column, Text } from '../../components/ui';
import { Colors } from '../../components/ui/styleUtils'; import { Colors } from '../../components/ui/styleUtils';
import { MainRouteProps } from '../../routes/main';
import { EditableListItem } from '../../components/EditableListItem'; import { EditableListItem } from '../../components/EditableListItem';
import { MessageOverlay } from '../../components/MessageOverlay'; import { MessageOverlay } from '../../components/MessageOverlay';
import { import { Credits } from './Credits';
ProfileScreenProps, import { Revoke } from './Revoke';
useProfileScreen, import { useProfileScreen } from './ProfileScreenController';
} from './ProfileScreenController'; import { useTranslation } from 'react-i18next';
import { LanguageSelector } from '../../components/LanguageSelector'; import { LanguageSelector } from '../../components/LanguageSelector';
import i18next, { SUPPORTED_LANGUAGES } from '../../i18n';
export const ProfileScreen: React.FC<ProfileScreenProps> = (props) => {
const { t } = useTranslation('ProfileLayout');
const controller = useProfileScreen(props);
return (
<React.Fragment>
<Column fill padding="24 0" backgroundColor={Colors.LightGrey}>
<EditableListItem
label={t('name')}
value={controller.name}
onEdit={controller.UPDATE_NAME}
/>
<EditableListItem
label={t('vcLabel')}
value={controller.vcLabel.singular}
onEdit={controller.UPDATE_VC_LABEL}
/>
<LanguageSetting />
<ListItem
bottomDivider
onPress={() => props.navigation.navigate('RevokeScreen')}>
<ListItem.Content>
<ListItem.Title>
<Text>{t('revokeScreen.title')}</Text>
</ListItem.Title>
</ListItem.Content>
</ListItem>
<ListItem bottomDivider disabled={!controller.canUseBiometrics}>
<ListItem.Content>
<ListItem.Title>
<Text>{t('bioUnlock')}</Text>
</ListItem.Title>
</ListItem.Content>
<Switch
value={controller.isBiometricUnlockEnabled}
onValueChange={controller.useBiometrics}
color={Colors.Orange}
/>
</ListItem>
<ListItem
bottomDivider
onPress={() => props.navigation.navigate('TransactionHistoryScreen')}>
<ListItem.Content>
<ListItem.Title>
<Text>{t('transactionHistoryScreen.title')}</Text>
</ListItem.Title>
</ListItem.Content>
</ListItem>
<ListItem bottomDivider disabled>
<ListItem.Content>
<ListItem.Title>
<Text color={Colors.Grey}>{t('authFactorUnlock')}</Text>
</ListItem.Title>
</ListItem.Content>
</ListItem>
<ListItem
bottomDivider
onPress={() => props.navigation.navigate('CreditsScreen')}>
<ListItem.Content>
<ListItem.Title>
<Text>{t('creditsScreen.title')}</Text>
</ListItem.Title>
</ListItem.Content>
</ListItem>
<ListItem bottomDivider onPress={controller.LOGOUT}>
<ListItem.Content>
<ListItem.Title>
<Text>{t('logout')}</Text>
</ListItem.Title>
</ListItem.Content>
</ListItem>
</Column>
<MessageOverlay
isVisible={controller.alertMsg != ''}
onBackdropPress={controller.hideAlert}
title={controller.alertMsg}
/>
</React.Fragment>
);
};
const LanguageSetting: React.FC = () => { const LanguageSetting: React.FC = () => {
const { t } = useTranslation('ProfileLayout'); const { t } = useTranslation('ProfileScreen');
return ( return (
<LanguageSelector <LanguageSelector
@@ -123,3 +34,83 @@ const LanguageSetting: React.FC = () => {
/> />
); );
}; };
export const ProfileScreen: React.FC<MainRouteProps> = (props) => {
const { t } = useTranslation('ProfileScreen');
const controller = useProfileScreen(props);
return (
<Column fill padding="24 0" backgroundColor={Colors.LightGrey}>
<MessageOverlay
isVisible={controller.alertMsg != ''}
onBackdropPress={controller.hideAlert}
title={controller.alertMsg}
/>
<EditableListItem
label={t('name')}
value={controller.name}
onEdit={controller.UPDATE_NAME}
/>
<EditableListItem
label={t('vcLabel')}
value={controller.vcLabel.singular}
onEdit={controller.UPDATE_VC_LABEL}
/>
<LanguageSetting />
<Revoke label={t('revokeLabel')} />
<ListItem bottomDivider disabled={!controller.canUseBiometrics}>
<ListItem.Content>
<ListItem.Title>
<Text>{t('bioUnlock')}</Text>
</ListItem.Title>
</ListItem.Content>
<Switch
value={controller.isBiometricUnlockEnabled}
onValueChange={controller.useBiometrics}
color={Colors.Orange}
/>
</ListItem>
<ListItem bottomDivider disabled>
<ListItem.Content>
<ListItem.Title>
<Text color={Colors.Grey}>{t('authFactorUnlock')}</Text>
</ListItem.Title>
</ListItem.Content>
</ListItem>
<Credits label={t('credits')} />
<ListItem bottomDivider onPress={controller.LOGOUT}>
<ListItem.Content>
<ListItem.Title>
<Text>{t('logout')}</Text>
</ListItem.Title>
</ListItem.Content>
</ListItem>
<Text
weight="semibold"
margin="32 0 0 0"
align="center"
size="smaller"
color={Colors.Grey}>
Version: {getVersion()}
</Text>
{controller.backendInfo.application.name !== '' ? (
<View>
<Text
weight="semibold"
align="center"
size="smaller"
color={Colors.Grey}>
{controller.backendInfo.application.name}:{' '}
{controller.backendInfo.application.version}
</Text>
<Text
weight="semibold"
align="center"
size="smaller"
color={Colors.Grey}>
MOSIP: {controller.backendInfo.config['mosip.host']}
</Text>
</View>
) : null}
</Column>
);
};

View File

@@ -1,8 +1,7 @@
import * as LocalAuthentication from 'expo-local-authentication';
import { useContext, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useMachine, useSelector } from '@xstate/react'; import { useMachine, useSelector } from '@xstate/react';
import { useContext, useEffect, useState } from 'react';
import * as LocalAuthentication from 'expo-local-authentication';
import { selectBackendInfo } from '../../machines/app';
import { import {
AuthEvents, AuthEvents,
selectBiometrics, selectBiometrics,
@@ -14,18 +13,18 @@ import {
selectVcLabel, selectVcLabel,
SettingsEvents, SettingsEvents,
} from '../../machines/settings'; } from '../../machines/settings';
import { import {
biometricsMachine, biometricsMachine,
selectError, selectError,
selectIsSuccess, selectIsSuccess,
selectUnenrolledNotice, selectUnenrolledNotice,
} from '../../machines/biometrics'; } from '../../machines/biometrics';
import { MainRouteProps } from '../../routes/main';
import { GlobalContext } from '../../shared/GlobalContext'; import { GlobalContext } from '../../shared/GlobalContext';
import { NativeStackScreenProps } from '@react-navigation/native-stack'; import { useTranslation } from 'react-i18next';
import { ProfileStackParamList } from './ProfileLayout';
import { RootStackParamList } from '../../routes';
export function useProfileScreen({ navigation }: ProfileScreenProps) { export function useProfileScreen({ navigation }: MainRouteProps) {
const { appService } = useContext(GlobalContext); const { appService } = useContext(GlobalContext);
const authService = appService.children.get('auth'); const authService = appService.children.get('auth');
const settingsService = appService.children.get('settings'); const settingsService = appService.children.get('settings');
@@ -94,7 +93,7 @@ export function useProfileScreen({ navigation }: ProfileScreenProps) {
return { return {
alertMsg, alertMsg,
hideAlert, hideAlert,
backendInfo: useSelector(appService, selectBackendInfo),
name: useSelector(settingsService, selectName), name: useSelector(settingsService, selectName),
vcLabel: useSelector(settingsService, selectVcLabel), vcLabel: useSelector(settingsService, selectVcLabel),
isBiometricUnlockEnabled: useSelector( isBiometricUnlockEnabled: useSelector(
@@ -119,8 +118,3 @@ export function useProfileScreen({ navigation }: ProfileScreenProps) {
}, },
}; };
} }
export type ProfileScreenProps = NativeStackScreenProps<
ProfileStackParamList & RootStackParamList,
'ProfileScreen'
>;

View File

@@ -6,19 +6,18 @@ import {
StyleSheet, StyleSheet,
View, View,
} from 'react-native'; } from 'react-native';
import { useTranslation } from 'react-i18next';
import { Divider, Icon, ListItem, Overlay } from 'react-native-elements'; import { Divider, Icon, ListItem, Overlay } from 'react-native-elements';
import { Button, Column, Centered, Row, Text } from '../../components/ui'; import { Button, Column, Centered, Row, Text } from '../../components/ui';
import { VidItem } from '../../components/VidItem'; import { VidItem } from '../../components/VidItem';
import { Colors } from '../../components/ui/styleUtils'; import { Colors } from '../../components/ui/styleUtils';
import { ToastItem } from '../../components/ui/ToastItem'; import { ToastItem } from '../../components/ui/ToastItem';
import { OIDcAuthenticationOverlay } from '../../components/OIDcAuthModal'; import { OIDcAuthenticationOverlay } from '../../components/OIDcAuthModal';
import { useRevokeScreen } from './RevokeScreenController'; import { useTranslation } from 'react-i18next';
import { useRevoke } from './RevokeController';
export const RevokeScreen: React.FC = () => { export const Revoke: React.FC<RevokeScreenProps> = (props) => {
const controller = useRevokeScreen(); const controller = useRevoke();
const { t } = useTranslation('ProfileLayout'); const { t } = useTranslation('ProfileScreen');
const styles = StyleSheet.create({ const styles = StyleSheet.create({
buttonContainer: { buttonContainer: {
@@ -53,10 +52,9 @@ export const RevokeScreen: React.FC = () => {
<ListItem bottomDivider onPress={() => controller.setAuthenticating(true)}> <ListItem bottomDivider onPress={() => controller.setAuthenticating(true)}>
<ListItem.Content> <ListItem.Content>
<ListItem.Title> <ListItem.Title>
<Text>{t('revokeLabel')}</Text> <Text>{props.label}</Text>
</ListItem.Title> </ListItem.Title>
</ListItem.Content> </ListItem.Content>
<Overlay <Overlay
overlayStyle={{ padding: 0 }} overlayStyle={{ padding: 0 }}
isVisible={controller.isViewing} isVisible={controller.isViewing}
@@ -185,3 +183,7 @@ export const RevokeScreen: React.FC = () => {
</ListItem> </ListItem>
); );
}; };
interface RevokeScreenProps {
label: string;
}

View File

@@ -19,7 +19,7 @@ import {
import { ActorRefFrom } from 'xstate'; import { ActorRefFrom } from 'xstate';
export function useRevokeScreen() { export function useRevoke() {
const { t } = useTranslation('ProfileScreen'); const { t } = useTranslation('ProfileScreen');
const { appService } = useContext(GlobalContext); const { appService } = useContext(GlobalContext);
const vcService = appService.children.get('vc'); const vcService = appService.children.get('vc');

View File

@@ -1,77 +0,0 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { FlatList, RefreshControl } from 'react-native';
import { Icon, Overlay } from 'react-native-elements';
import { TransactionHistoryItem } from '../../components/TransactionHistoryItem';
import { Column, Row, Text } from '../../components/ui';
import { Colors } from '../../components/ui/styleUtils';
import { useTransactionHistoryScreen } from './TransactionHistoryScreenController';
export const TransactionHistoryScreen: React.FC = () => {
const controller = useTransactionHistoryScreen();
const numRecords = controller.transactionHistory.length;
return (
<React.Fragment>
<Column backgroundColor={Colors.Grey6} fill>
<Row margin="16">
<Column fill align="center">
<StatusText
isLoading={controller.isLoading}
numRecords={numRecords}
/>
</Column>
<Column>
<Icon
name="filter"
color={numRecords > 0 ? Colors.Orange : Colors.Grey}
onPress={() => numRecords > 0 && controller.SHOW_FILTERS()}
/>
</Column>
</Row>
<Column fill>
<FlatList
data={controller.transactionHistory}
keyExtractor={(item) => item.eventId}
renderItem={({ item }) => <TransactionHistoryItem data={item} />}
refreshControl={
<RefreshControl
refreshing={controller.isLoading}
onRefresh={controller.REFRESH}
/>
}
/>
</Column>
</Column>
<Overlay
isVisible={controller.isShowingFilters}
onBackdropPress={controller.CANCEL}>
<Text>TODO: Filters Modal</Text>
{/* FILTERS */}
</Overlay>
</React.Fragment>
);
};
const StatusText: React.FC<{ isLoading: boolean; numRecords: number }> = (
props
) => {
const { t } = useTranslation('ProfileLayout');
const { isLoading, numRecords } = props;
return isLoading ? (
<Text color={Colors.Grey} size="small">
{t('transactionHistoryScreen.fetchingRecords')}
</Text>
) : (
<Text color={Colors.Grey} size="small">
{numRecords > 0
? t('transactionHistoryScreen.showingRecords', {
numRecords,
})
: t('transactionHistoryScreen.noRecords')}
</Text>
);
};

View File

@@ -1,187 +0,0 @@
import { useInterpret, useSelector } from '@xstate/react';
import { useRef } from 'react';
import { assign, StateFrom } from 'xstate';
import { createModel } from 'xstate/lib/model';
import { request } from '../../shared/request';
export function useTransactionHistoryScreen() {
const machine = useRef(TransactionHistoryScreenMachine);
const service = useInterpret(machine.current, { devTools: __DEV__ });
return {
transactionHistory: useSelector(service, selectTransactionHistory),
isLoading: useSelector(service, selectIsLoading),
isShowingFilters: useSelector(service, selectIsShowingFilters),
REFRESH: () => service.send(TransactionHistoryScreenEvents.REFRESH()),
CANCEL: () => service.send(TransactionHistoryScreenEvents.CANCEL()),
SHOW_FILTERS: () =>
service.send(TransactionHistoryScreenEvents.SHOW_FILTERS()),
APPLY_FILTERS: (filters: TransactionHistoryFilters) =>
service.send(TransactionHistoryScreenEvents.APPLY_FILTERS(filters)),
};
}
const model = createModel(
{
items: [] as MOSIPServiceHistoryItem[],
error: '',
filters: {
pageStart: 0,
pageFetch: 50,
searchText: '',
sortType: 'DESC',
} as TransactionHistoryFilters,
},
{
events: {
REFRESH: () => ({}),
CANCEL: () => ({}),
SHOW_FILTERS: () => ({}),
APPLY_FILTERS: (filters: TransactionHistoryFilters) => ({ filters }),
},
}
);
export const TransactionHistoryScreenEvents = model.events;
export const TransactionHistoryScreenMachine = model.createMachine(
{
tsTypes:
{} as import('./TransactionHistoryScreenController.typegen').Typegen0,
id: 'TransactionHistoryScreen',
context: model.initialContext,
schema: {
services: {} as {
loadTransactionHistory: {
data: MOSIPServiceHistoryItem[];
};
},
},
initial: 'loading',
states: {
loading: {
invoke: {
src: 'loadTransactionHistory',
onDone: {
target: 'idle',
actions: 'setItems',
},
onError: {
target: 'idle',
actions: 'setError',
},
},
},
idle: {
on: {
REFRESH: {
target: 'loading',
},
SHOW_FILTERS: {
target: 'showingFilters',
},
},
},
showingFilters: {
on: {
CANCEL: {
target: 'idle',
},
APPLY_FILTERS: {
actions: 'setFilters',
target: 'idle',
},
},
},
},
},
{
actions: {
setItems: assign({
items: (_context, event) => event.data,
}),
setError: assign({
error: (_context, event) => (event.data as Error).message,
}),
setFilters: assign({
filters: (_context, event) => event.filters,
}),
},
services: {
loadTransactionHistory: (context) => async () => {
const langCode = 'en-US';
console.log('a');
const query = toQueryParams(context.filters);
console.log('b', query);
const response = await request(
'GET',
`/req/service-history/${langCode}?${query}`
);
console.log('c', response);
return response.response.data;
},
},
}
);
type State = StateFrom<typeof TransactionHistoryScreenMachine>;
function selectTransactionHistory(state: State) {
return state.context.items;
}
function selectIsLoading(state: State) {
return state.matches('loading');
}
function selectIsShowingFilters(state: State) {
return state.matches('showingFilters');
}
export interface MOSIPServiceHistoryItem {
eventId: string;
description: string;
eventStatus: MOSIPServiceHistoryItemEventStatus;
timeStamp: string; // JSON Date
requestType: string;
}
export enum MOSIPServiceHistoryItemEventStatus {
Success = 'Success',
Failed = 'Failed',
InProgress = 'In-Progress',
}
export enum MOSIPServiceHistoryItemType {
All = 'ALL',
AuthenticationRequest = 'AUTHENTICATION_REQUEST',
ServiceRequest = 'SERVICE_REQUEST',
DataUpdateRequest = 'DATA_UPDATE_REQUEST',
IdManagementRequest = 'ID_MANAGEMENT_REQUEST',
DataShareRequest = 'DATA_SHARE_REQUEST',
}
export interface TransactionHistoryFilters {
fromDateTime?: string;
toDateTime?: string;
pageFetch?: number;
pageStart?: number;
searchText?: string;
serviceType?: MOSIPServiceHistoryItemType;
sortType?: 'ASC' | 'DESC';
[key: string]: unknown;
}
function toQueryParams(queryObj: Record<string, unknown>) {
return Object.entries(queryObj)
.map(([key, value]) => `${key}=${value}`)
.join('&');
}

View File

@@ -1,38 +0,0 @@
// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
'@@xstate/typegen': true;
'internalEvents': {
'done.invoke.TransactionHistoryScreen.loading:invocation[0]': {
type: 'done.invoke.TransactionHistoryScreen.loading:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'error.platform.TransactionHistoryScreen.loading:invocation[0]': {
type: 'error.platform.TransactionHistoryScreen.loading:invocation[0]';
data: unknown;
};
'xstate.init': { type: 'xstate.init' };
};
'invokeSrcNameMap': {
loadTransactionHistory: 'done.invoke.TransactionHistoryScreen.loading:invocation[0]';
};
'missingImplementations': {
actions: never;
services: never;
guards: never;
delays: never;
};
'eventsCausingActions': {
setError: 'error.platform.TransactionHistoryScreen.loading:invocation[0]';
setFilters: 'APPLY_FILTERS';
setItems: 'done.invoke.TransactionHistoryScreen.loading:invocation[0]';
};
'eventsCausingServices': {
loadTransactionHistory: 'REFRESH' | 'xstate.init';
};
'eventsCausingGuards': {};
'eventsCausingDelays': {};
'matchesStates': 'idle' | 'loading' | 'showingFilters';
'tags': never;
}

View File

@@ -1,4 +0,0 @@
declare module '*.md' {
const content: string;
export default content;
}