Files
ppc_amnet/amnet-native/app/card/ManualCardEntry.tsx
T

128 lines
5.1 KiB
TypeScript

import {generateCardNumber, DecodeAMAccessCode} from "amnet-shared";
import {useCallback, useEffect, useLayoutEffect, useState} from "react";
import {Alert, Platform, TextInput, TouchableOpacity, View} from "react-native";
import {Button, Card, Divider, Paragraph, useTheme} from "react-native-paper";
import {AMCard} from "../../models/AMCard";
import RealmContext from "../../models/RealmContext";
import {useModalStyles} from "../../utils/ModalStyles";
const {useRealm} = RealmContext;
export default function CardAddModal({navigation, route}) {
const realm = useRealm();
const theme = useTheme();
const modalStyles = useModalStyles();
// state values
const [cardName, setCardName] = useState('');
const [cardNumber, setCardNumber] = useState('');
const cardAddCallback = useCallback(async (cardId: string, cardName: string) => {
try {
// validate AM card numbers
cardId.startsWith('5') && DecodeAMAccessCode(cardId);
} catch {
const shouldContinue = await new Promise<boolean>(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>(AMCard.schema.name).find(x => x.code === cardId);
if (duplicateCard) {
realm.write(() => {
duplicateCard.name = cardName;
});
} else {
realm.write(() => {
realm.create<AMCard>(AMCard.schema.name, AMCard.generate(cardId, cardName));
});
}
if (Platform.OS === 'ios') {
navigation.getParent()?.goBack();
} else {
navigation.goBack();
}
}, [realm]);
useLayoutEffect(() => {
const canSave = cardName.length && cardNumber.length === 20;
const themeOptions = canSave ? modalStyles.headerText : {color: theme.colors.outline};
navigation.setOptions({
headerRight: () => <TouchableOpacity disabled={!canSave}
onPress={() => cardAddCallback(cardNumber, cardName)}>
<Paragraph style={{...themeOptions, fontWeight: "600"}}>Save</Paragraph>
</TouchableOpacity>
})
}, [navigation, modalStyles, cardName, cardNumber]);
// check params for autoGenerate request
useEffect(() => {
if (route.params?.autoGenerate) {
setCardNumber(generateCardNumber());
}
}, []);
return (
<View style={modalStyles.rootLayout}>
<Card mode="contained">
<Card.Content>
<View style={modalStyles.formItemRow}>
<Paragraph style={modalStyles.formItemName}>
Card Name
</Paragraph>
<TextInput style={modalStyles.formItemInput}
autoFocus
numberOfLines={1}
value={cardName}
onChangeText={setCardName}
placeholder="Primary Card"
placeholderTextColor={theme.colors.outline}/>
</View>
<Divider style={{marginVertical: 15}}/>
<View style={modalStyles.formItemRow}>
<Paragraph style={modalStyles.formItemName}>
Access Code
</Paragraph>
<TextInput numberOfLines={1}
keyboardType="numeric"
placeholder="0000 0000 0000 0000 0000"
placeholderTextColor={theme.colors.outline}
value={cardNumber.match(/.{1,4}/g)?.join(" ")}
onChangeText={val => setCardNumber(val?.replaceAll(/\D/g, '').substring(0, 20) ?? '')}
style={{...modalStyles.formItemInput, color: theme.colors.onSurface}}/>
</View>
</Card.Content>
</Card>
<Button mode="text" onPress={() => setCardNumber(generateCardNumber())}>Generate Access Code</Button>
</View>
)
}