diff --git a/amnet-native/app/card/ManualCardEntry.tsx b/amnet-native/app/card/ManualCardEntry.tsx index 92ad58d..ed8b34c 100644 --- a/amnet-native/app/card/ManualCardEntry.tsx +++ b/amnet-native/app/card/ManualCardEntry.tsx @@ -1,6 +1,6 @@ -import {generateCardNumber} from "amnet-shared"; +import {generateCardNumber, DecodeAMAccessCode} from "amnet-shared"; import {useCallback, useEffect, useLayoutEffect, useState} from "react"; -import {Platform, TextInput, TouchableOpacity, View} from "react-native"; +import {Alert, Platform, TextInput, TouchableOpacity, View} from "react-native"; import {Button, Card, Divider, Paragraph, useTheme} from "react-native-paper"; import {AMCard} from "../../models/AMCard"; @@ -18,7 +18,37 @@ export default function CardAddModal({navigation, route}) { const [cardName, setCardName] = useState(''); const [cardNumber, setCardNumber] = useState(''); - const cardAddCallback = useCallback((cardId: string, cardName: string) => { + const cardAddCallback = useCallback(async (cardId: string, cardName: string) => { + try { + // validate AM card numbers + cardId.startsWith('5') && DecodeAMAccessCode(cardId); + } catch { + const shouldContinue = await new Promise(done => { + Alert.alert( + "Invalid Access Code", + "It looks like the access code isn't from an Amusement IC card. Please double-check the code, and if it's correct, note that some networks might still reject it.", + [ + { + style: "destructive", + text: "Continue Anyway", + onPress: () => done(true) + }, + { + style: "cancel", + text: "Cancel", + onPress: () => done(false) + } + ], { + cancelable: true, + onDismiss: () => done(false) + }); + }) + + if (!shouldContinue) { + return; + } + } + const duplicateCard = realm.objects(AMCard.schema.name).find(x => x.code === cardId); if (duplicateCard) { @@ -43,7 +73,8 @@ export default function CardAddModal({navigation, route}) { const themeOptions = canSave ? modalStyles.headerText : {color: theme.colors.outline}; navigation.setOptions({ - headerRight: () => cardAddCallback(cardNumber, cardName)}> + headerRight: () => cardAddCallback(cardNumber, cardName)}> Save }) @@ -90,7 +121,6 @@ export default function CardAddModal({navigation, route}) { - {/* only show card entry info when in create mode */} ) diff --git a/amnet-native/metro.config.js b/amnet-native/metro.config.js index 492244c..06fa400 100644 --- a/amnet-native/metro.config.js +++ b/amnet-native/metro.config.js @@ -6,7 +6,8 @@ module.exports = (async () => { const config = await getDefaultConfig(__dirname); config.resolver.extraNodeModules = { - 'amnet-shared': resolve(__dirname, '../amnet-shared') + 'amnet-shared': resolve(__dirname, '../amnet-shared'), + '@babel/runtime': resolve(__dirname, 'node_modules/@babel/runtime') }; config.watchFolders = [ diff --git a/amnet-native/package-lock.json b/amnet-native/package-lock.json index 7fba1f4..df113e0 100644 --- a/amnet-native/package-lock.json +++ b/amnet-native/package-lock.json @@ -9,6 +9,7 @@ "version": "1.1.0", "hasInstallScript": true, "dependencies": { + "@babel/runtime": "^7.26.0", "@expo-google-fonts/jetbrains-mono": "^0.2.3", "@react-navigation/bottom-tabs": "^6.6.1", "@react-navigation/native": "^6.1.18", @@ -47,7 +48,12 @@ }, "../amnet-shared": { "version": "1.0.0", + "dependencies": { + "big-integer": "^1.6.52", + "lodash": "^4.17.21" + }, "devDependencies": { + "@types/lodash": "^4.17.14", "tsup": "^8.2.4", "typescript": "^5.5.4" } diff --git a/amnet-native/package.json b/amnet-native/package.json index 481af55..3ea5570 100644 --- a/amnet-native/package.json +++ b/amnet-native/package.json @@ -9,6 +9,7 @@ "postinstall": "patch-package" }, "dependencies": { + "@babel/runtime": "^7.26.0", "@expo-google-fonts/jetbrains-mono": "^0.2.3", "@react-navigation/bottom-tabs": "^6.6.1", "@react-navigation/native": "^6.1.18",