expo-openpay 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +5 -0
- package/LICENSE +21 -0
- package/README.md +87 -0
- package/android/build.gradle +76 -0
- package/android/gradle.properties +2 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/expo/modules/openpay/ExpoOpenpayModule.kt +127 -0
- package/android/src/main/java/expo/modules/openpay/ExpoOpenpayView.kt +30 -0
- package/android/src/main/java/mx/openpay/android/BuildConfig.java +8 -0
- package/android/src/main/java/mx/openpay/android/DeviceCollectorDefaultImpl.java +70 -0
- package/android/src/main/java/mx/openpay/android/JavaScriptInterface.java +19 -0
- package/android/src/main/java/mx/openpay/android/OpCountry.java +24 -0
- package/android/src/main/java/mx/openpay/android/OpenPayResult.java +35 -0
- package/android/src/main/java/mx/openpay/android/Openpay.java +98 -0
- package/android/src/main/java/mx/openpay/android/OpenpayUrls.java +11 -0
- package/android/src/main/java/mx/openpay/android/OperationCallBack.java +13 -0
- package/android/src/main/java/mx/openpay/android/OperationResult.java +14 -0
- package/android/src/main/java/mx/openpay/android/exceptions/OpenpayServiceException.java +93 -0
- package/android/src/main/java/mx/openpay/android/exceptions/ServiceUnavailableException.java +23 -0
- package/android/src/main/java/mx/openpay/android/model/Address.java +123 -0
- package/android/src/main/java/mx/openpay/android/model/Card.java +219 -0
- package/android/src/main/java/mx/openpay/android/model/Token.java +43 -0
- package/android/src/main/java/mx/openpay/android/services/BaseService.java +93 -0
- package/android/src/main/java/mx/openpay/android/services/ServicesFactory.java +47 -0
- package/android/src/main/java/mx/openpay/android/services/TokenService.java +19 -0
- package/android/src/main/java/mx/openpay/android/validation/CardType.java +9 -0
- package/android/src/main/java/mx/openpay/android/validation/CardValidator.java +105 -0
- package/android/src/main/java/mx/openpay/android/validation/LuhnValidator.java +34 -0
- package/build/ExpoOpenpay.types.d.ts +42 -0
- package/build/ExpoOpenpay.types.d.ts.map +1 -0
- package/build/ExpoOpenpay.types.js +2 -0
- package/build/ExpoOpenpay.types.js.map +1 -0
- package/build/ExpoOpenpayModule.d.ts +10 -0
- package/build/ExpoOpenpayModule.d.ts.map +1 -0
- package/build/ExpoOpenpayModule.js +6 -0
- package/build/ExpoOpenpayModule.js.map +1 -0
- package/build/ExpoOpenpayModule.web.d.ts +10 -0
- package/build/ExpoOpenpayModule.web.d.ts.map +1 -0
- package/build/ExpoOpenpayModule.web.js +12 -0
- package/build/ExpoOpenpayModule.web.js.map +1 -0
- package/build/ExpoOpenpayView.d.ts +4 -0
- package/build/ExpoOpenpayView.d.ts.map +1 -0
- package/build/ExpoOpenpayView.js +7 -0
- package/build/ExpoOpenpayView.js.map +1 -0
- package/build/ExpoOpenpayView.web.d.ts +3 -0
- package/build/ExpoOpenpayView.web.d.ts.map +1 -0
- package/build/ExpoOpenpayView.web.js +5 -0
- package/build/ExpoOpenpayView.web.js.map +1 -0
- package/build/assets/AmexLogo.d.ts +4 -0
- package/build/assets/AmexLogo.d.ts.map +1 -0
- package/build/assets/AmexLogo.js +9 -0
- package/build/assets/AmexLogo.js.map +1 -0
- package/build/assets/MCLogo.d.ts +4 -0
- package/build/assets/MCLogo.d.ts.map +1 -0
- package/build/assets/MCLogo.js +9 -0
- package/build/assets/MCLogo.js.map +1 -0
- package/build/assets/VisaLogo.d.ts +4 -0
- package/build/assets/VisaLogo.d.ts.map +1 -0
- package/build/assets/VisaLogo.js +6 -0
- package/build/assets/VisaLogo.js.map +1 -0
- package/build/assets/index.d.ts +4 -0
- package/build/assets/index.d.ts.map +1 -0
- package/build/assets/index.js +4 -0
- package/build/assets/index.js.map +1 -0
- package/build/components/OPCardForm.d.ts +33 -0
- package/build/components/OPCardForm.d.ts.map +1 -0
- package/build/components/OPCardForm.js +120 -0
- package/build/components/OPCardForm.js.map +1 -0
- package/build/components/OPCardNumberInput.d.ts +17 -0
- package/build/components/OPCardNumberInput.d.ts.map +1 -0
- package/build/components/OPCardNumberInput.js +60 -0
- package/build/components/OPCardNumberInput.js.map +1 -0
- package/build/components/OPCvv2Input.d.ts +17 -0
- package/build/components/OPCvv2Input.d.ts.map +1 -0
- package/build/components/OPCvv2Input.js +13 -0
- package/build/components/OPCvv2Input.js.map +1 -0
- package/build/components/OPExpInput.d.ts +17 -0
- package/build/components/OPExpInput.d.ts.map +1 -0
- package/build/components/OPExpInput.js +22 -0
- package/build/components/OPExpInput.js.map +1 -0
- package/build/components/OPExpMonthInput.d.ts +17 -0
- package/build/components/OPExpMonthInput.d.ts.map +1 -0
- package/build/components/OPExpMonthInput.js +6 -0
- package/build/components/OPExpMonthInput.js.map +1 -0
- package/build/components/OPExpYearInput.d.ts +17 -0
- package/build/components/OPExpYearInput.d.ts.map +1 -0
- package/build/components/OPExpYearInput.js +6 -0
- package/build/components/OPExpYearInput.js.map +1 -0
- package/build/components/OPHolderNameInput.d.ts +17 -0
- package/build/components/OPHolderNameInput.d.ts.map +1 -0
- package/build/components/OPHolderNameInput.js +6 -0
- package/build/components/OPHolderNameInput.js.map +1 -0
- package/build/components/forms/ErrorMessage.d.ts +7 -0
- package/build/components/forms/ErrorMessage.d.ts.map +1 -0
- package/build/components/forms/ErrorMessage.js +12 -0
- package/build/components/forms/ErrorMessage.js.map +1 -0
- package/build/components/forms/FormField.d.ts +34 -0
- package/build/components/forms/FormField.d.ts.map +1 -0
- package/build/components/forms/FormField.js +33 -0
- package/build/components/forms/FormField.js.map +1 -0
- package/build/components/forms/SubmitButton.d.ts +11 -0
- package/build/components/forms/SubmitButton.d.ts.map +1 -0
- package/build/components/forms/SubmitButton.js +45 -0
- package/build/components/forms/SubmitButton.js.map +1 -0
- package/build/components/forms/Text.d.ts +7 -0
- package/build/components/forms/Text.d.ts.map +1 -0
- package/build/components/forms/Text.js +12 -0
- package/build/components/forms/Text.js.map +1 -0
- package/build/components/forms/TextInput.d.ts +30 -0
- package/build/components/forms/TextInput.d.ts.map +1 -0
- package/build/components/forms/TextInput.js +195 -0
- package/build/components/forms/TextInput.js.map +1 -0
- package/build/components/forms/index.d.ts +4 -0
- package/build/components/forms/index.d.ts.map +1 -0
- package/build/components/forms/index.js +5 -0
- package/build/components/forms/index.js.map +1 -0
- package/build/components/index.d.ts +8 -0
- package/build/components/index.d.ts.map +1 -0
- package/build/components/index.js +8 -0
- package/build/components/index.js.map +1 -0
- package/build/constants/Colors.d.ts +27 -0
- package/build/constants/Colors.d.ts.map +1 -0
- package/build/constants/Colors.js +27 -0
- package/build/constants/Colors.js.map +1 -0
- package/build/constants/Styles.d.ts +9 -0
- package/build/constants/Styles.d.ts.map +1 -0
- package/build/constants/Styles.js +9 -0
- package/build/constants/Styles.js.map +1 -0
- package/build/context/FormContext.d.ts +22 -0
- package/build/context/FormContext.d.ts.map +1 -0
- package/build/context/FormContext.js +63 -0
- package/build/context/FormContext.js.map +1 -0
- package/build/context/FormThemeContext.d.ts +20 -0
- package/build/context/FormThemeContext.d.ts.map +1 -0
- package/build/context/FormThemeContext.js +21 -0
- package/build/context/FormThemeContext.js.map +1 -0
- package/build/context/index.d.ts +4 -0
- package/build/context/index.d.ts.map +1 -0
- package/build/context/index.js +3 -0
- package/build/context/index.js.map +1 -0
- package/build/index.d.ts +6 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +18 -0
- package/build/index.js.map +1 -0
- package/build/utility/cardBranding.d.ts +2 -0
- package/build/utility/cardBranding.d.ts.map +1 -0
- package/build/utility/cardBranding.js +2 -0
- package/build/utility/cardBranding.js.map +1 -0
- package/build/utility/formatting.d.ts +4 -0
- package/build/utility/formatting.d.ts.map +1 -0
- package/build/utility/formatting.js +20 -0
- package/build/utility/formatting.js.map +1 -0
- package/expo-module.config.json +9 -0
- package/ios/ExpoOpenpay.podspec +30 -0
- package/ios/Frameworks/OpenpayKit.xcframework/Info.plist +40 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64/OpenpayKit.framework/Headers/OpenpayKit-Swift.h +274 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64/OpenpayKit.framework/Info.plist +0 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64/OpenpayKit.framework/Modules/OpenpayKit.swiftmodule/arm64-apple-ios.abi.json +5179 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64/OpenpayKit.framework/Modules/OpenpayKit.swiftmodule/arm64-apple-ios.private.swiftinterface +127 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64/OpenpayKit.framework/Modules/OpenpayKit.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64/OpenpayKit.framework/Modules/OpenpayKit.swiftmodule/arm64-apple-ios.swiftinterface +127 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64/OpenpayKit.framework/Modules/module.modulemap +4 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64/OpenpayKit.framework/OpenpayKit +0 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64/OpenpayKit.framework/_CodeSignature/CodeResources +287 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64/OpenpayKit.framework/en.lproj/CardView.nib +0 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64/OpenpayKit.framework/en.lproj/Localizable.strings +0 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64/OpenpayKit.framework/es-MX.lproj/CardView.nib +0 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64/OpenpayKit.framework/es-MX.lproj/Localizable.strings +0 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64/OpenpayKit.framework/es.lproj/CardView.nib +0 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64/OpenpayKit.framework/es.lproj/Localizable.strings +0 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64_x86_64-simulator/OpenpayKit.framework/Headers/OpenpayKit-Swift.h +544 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64_x86_64-simulator/OpenpayKit.framework/Info.plist +0 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64_x86_64-simulator/OpenpayKit.framework/Modules/OpenpayKit.swiftmodule/arm64-apple-ios-simulator.abi.json +5179 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64_x86_64-simulator/OpenpayKit.framework/Modules/OpenpayKit.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +127 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64_x86_64-simulator/OpenpayKit.framework/Modules/OpenpayKit.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64_x86_64-simulator/OpenpayKit.framework/Modules/OpenpayKit.swiftmodule/arm64-apple-ios-simulator.swiftinterface +127 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64_x86_64-simulator/OpenpayKit.framework/Modules/OpenpayKit.swiftmodule/x86_64-apple-ios-simulator.abi.json +5179 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64_x86_64-simulator/OpenpayKit.framework/Modules/OpenpayKit.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +127 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64_x86_64-simulator/OpenpayKit.framework/Modules/OpenpayKit.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64_x86_64-simulator/OpenpayKit.framework/Modules/OpenpayKit.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +127 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64_x86_64-simulator/OpenpayKit.framework/Modules/module.modulemap +4 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64_x86_64-simulator/OpenpayKit.framework/OpenpayKit +0 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64_x86_64-simulator/OpenpayKit.framework/_CodeSignature/CodeResources +342 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64_x86_64-simulator/OpenpayKit.framework/en.lproj/CardView.nib +0 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64_x86_64-simulator/OpenpayKit.framework/en.lproj/Localizable.strings +0 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64_x86_64-simulator/OpenpayKit.framework/es-MX.lproj/CardView.nib +0 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64_x86_64-simulator/OpenpayKit.framework/es-MX.lproj/Localizable.strings +0 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64_x86_64-simulator/OpenpayKit.framework/es.lproj/CardView.nib +0 -0
- package/ios/Frameworks/OpenpayKit.xcframework/ios-arm64_x86_64-simulator/OpenpayKit.framework/es.lproj/Localizable.strings +0 -0
- package/ios/src/ExpoOpenpayModule.swift +105 -0
- package/ios/src/ExpoOpenpayView.swift +38 -0
- package/package.json +46 -0
- package/src/ExpoOpenpay.types.ts +47 -0
- package/src/ExpoOpenpayModule.ts +29 -0
- package/src/ExpoOpenpayModule.web.ts +15 -0
- package/src/ExpoOpenpayView.tsx +11 -0
- package/src/ExpoOpenpayView.web.tsx +5 -0
- package/src/assets/AmexLogo.tsx +24 -0
- package/src/assets/MCLogo.tsx +19 -0
- package/src/assets/VisaLogo.tsx +11 -0
- package/src/assets/index.ts +3 -0
- package/src/assets/photos/discover_logo.png +0 -0
- package/src/components/OPCardForm.tsx +303 -0
- package/src/components/OPCardNumberInput.tsx +126 -0
- package/src/components/OPCvv2Input.tsx +62 -0
- package/src/components/OPExpInput.tsx +74 -0
- package/src/components/OPExpMonthInput.tsx +55 -0
- package/src/components/OPExpYearInput.tsx +55 -0
- package/src/components/OPHolderNameInput.tsx +53 -0
- package/src/components/forms/ErrorMessage.tsx +19 -0
- package/src/components/forms/FormField.tsx +96 -0
- package/src/components/forms/SubmitButton.tsx +81 -0
- package/src/components/forms/Text.tsx +20 -0
- package/src/components/forms/TextInput.tsx +329 -0
- package/src/components/forms/index.ts +4 -0
- package/src/components/index.ts +7 -0
- package/src/constants/Colors.ts +26 -0
- package/src/constants/Styles.ts +9 -0
- package/src/context/FormContext.tsx +109 -0
- package/src/context/FormThemeContext.tsx +55 -0
- package/src/context/index.ts +3 -0
- package/src/index.ts +19 -0
- package/src/utility/cardBranding.ts +0 -0
- package/src/utility/formatting.ts +23 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { StyleProp, TextStyle, ViewStyle } from "react-native";
|
|
3
|
+
|
|
4
|
+
import { FormField } from "./forms";
|
|
5
|
+
|
|
6
|
+
type IconType =
|
|
7
|
+
| {
|
|
8
|
+
type: "fa6";
|
|
9
|
+
name:
|
|
10
|
+
| "cc-visa"
|
|
11
|
+
| "cc-mastercard"
|
|
12
|
+
| "cc-amex"
|
|
13
|
+
| "cc-discover"
|
|
14
|
+
| "credit-card";
|
|
15
|
+
}
|
|
16
|
+
| { type: "material"; name: "credit-card" }
|
|
17
|
+
| { type: "png"; name: "amex" | "discover" | "mc" | "visa" };
|
|
18
|
+
|
|
19
|
+
type CardInputOptions = {
|
|
20
|
+
options?: {
|
|
21
|
+
formFieldStyle?: StyleProp<ViewStyle>;
|
|
22
|
+
defaultStyling?: boolean;
|
|
23
|
+
inputStyle?: StyleProp<TextStyle>;
|
|
24
|
+
labelStyle?: StyleProp<TextStyle>;
|
|
25
|
+
textInputStyle?: StyleProp<TextStyle>;
|
|
26
|
+
};
|
|
27
|
+
withAnimatedText?: boolean;
|
|
28
|
+
withIcon?: boolean;
|
|
29
|
+
withLabel?: boolean;
|
|
30
|
+
withPlaceholder?: boolean;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function getCardBrandIcon(cardType: string | null): IconType {
|
|
34
|
+
switch (cardType) {
|
|
35
|
+
case "visa":
|
|
36
|
+
return { type: "png", name: "visa" };
|
|
37
|
+
case "mastercard":
|
|
38
|
+
return { type: "png", name: "mc" };
|
|
39
|
+
case "amex":
|
|
40
|
+
return { type: "png", name: "amex" };
|
|
41
|
+
case "discover":
|
|
42
|
+
return { type: "png", name: "discover" };
|
|
43
|
+
default:
|
|
44
|
+
return { type: "material", name: "credit-card" };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function detectCardBrand(cardNumber: string): string | null {
|
|
49
|
+
const sanitized = cardNumber.replace(/\s+/g, "");
|
|
50
|
+
|
|
51
|
+
if (/^4\d{0,15}$/.test(sanitized)) return "visa";
|
|
52
|
+
if (/^(5[1-5]|2[2-7])\d{0,14}$/.test(sanitized)) return "mastercard";
|
|
53
|
+
if (/^3[47]\d{0,13}$/.test(sanitized)) return "amex";
|
|
54
|
+
if (/^6(?:011|5\d{2})\d{0,12}$/.test(sanitized)) return "discover";
|
|
55
|
+
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Luhn algorithm validator
|
|
60
|
+
function validateCardNumber(value: string) {
|
|
61
|
+
if (!value) return "Card Number is required";
|
|
62
|
+
|
|
63
|
+
const sanitized = value.replace(/\s+/g, "");
|
|
64
|
+
if (sanitized.length < 13 || sanitized.length > 19) {
|
|
65
|
+
return "Card Number must be 13-19 digits";
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let sum = 0;
|
|
69
|
+
let shouldDouble = false;
|
|
70
|
+
for (let i = sanitized.length - 1; i >= 0; i--) {
|
|
71
|
+
let digit = parseInt(sanitized[i], 10);
|
|
72
|
+
if (shouldDouble) {
|
|
73
|
+
digit *= 2;
|
|
74
|
+
if (digit > 9) digit -= 9;
|
|
75
|
+
}
|
|
76
|
+
sum += digit;
|
|
77
|
+
shouldDouble = !shouldDouble;
|
|
78
|
+
}
|
|
79
|
+
if (sum % 10 !== 0) return "Invalid card number";
|
|
80
|
+
|
|
81
|
+
return undefined; // valid card
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export default function OPCardNumberInput({
|
|
85
|
+
options = {},
|
|
86
|
+
withAnimatedText = true,
|
|
87
|
+
withIcon = true,
|
|
88
|
+
withLabel = false,
|
|
89
|
+
withPlaceholder = true,
|
|
90
|
+
...otherProps
|
|
91
|
+
}: CardInputOptions) {
|
|
92
|
+
const [cardType, setCardType] = useState<string | null>(null);
|
|
93
|
+
|
|
94
|
+
const {
|
|
95
|
+
defaultStyling = true,
|
|
96
|
+
formFieldStyle = {},
|
|
97
|
+
inputStyle = {},
|
|
98
|
+
labelStyle = {},
|
|
99
|
+
textInputStyle = {},
|
|
100
|
+
} = options;
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<FormField
|
|
104
|
+
autoCapitalize="none"
|
|
105
|
+
autoComplete="cc-number"
|
|
106
|
+
autoCorrect={false}
|
|
107
|
+
defaultStyling={defaultStyling}
|
|
108
|
+
icon={getCardBrandIcon(cardType)}
|
|
109
|
+
keyboardType="number-pad"
|
|
110
|
+
labelStyle={labelStyle}
|
|
111
|
+
maxLength={19}
|
|
112
|
+
name="cardNumber"
|
|
113
|
+
onValueChange={(text) => {
|
|
114
|
+
setCardType(detectCardBrand(text));
|
|
115
|
+
}}
|
|
116
|
+
placeholder="Card Number"
|
|
117
|
+
styling={{ formFieldStyle, inputStyle, textInputStyle }}
|
|
118
|
+
validate={validateCardNumber}
|
|
119
|
+
withAnimatedText={withAnimatedText}
|
|
120
|
+
withIcon={withIcon}
|
|
121
|
+
withLabel={withLabel}
|
|
122
|
+
withPlaceholder={withPlaceholder}
|
|
123
|
+
{...otherProps}
|
|
124
|
+
/>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { StyleProp, ViewStyle, TextStyle } from "react-native";
|
|
2
|
+
|
|
3
|
+
import { FormField } from "./forms";
|
|
4
|
+
|
|
5
|
+
type CardInputOptions = {
|
|
6
|
+
options?: {
|
|
7
|
+
formFieldStyle?: StyleProp<ViewStyle>;
|
|
8
|
+
defaultStyling?: boolean;
|
|
9
|
+
inputStyle?: StyleProp<TextStyle>;
|
|
10
|
+
labelStyle?: StyleProp<TextStyle>;
|
|
11
|
+
textInputStyle?: StyleProp<TextStyle>;
|
|
12
|
+
};
|
|
13
|
+
withAnimatedText?: boolean;
|
|
14
|
+
withIcon?: boolean;
|
|
15
|
+
withLabel?: boolean;
|
|
16
|
+
withPlaceholder?: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function validateCvv(value: string) {
|
|
20
|
+
if (!value) return "CVV is required";
|
|
21
|
+
if (!/^\d{3,4}$/.test(value)) return "Invalid CVV";
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default function OPExpYearInput({
|
|
26
|
+
options = {},
|
|
27
|
+
withAnimatedText = true,
|
|
28
|
+
withIcon = true,
|
|
29
|
+
withLabel = false,
|
|
30
|
+
withPlaceholder = true,
|
|
31
|
+
...otherProps
|
|
32
|
+
}: CardInputOptions) {
|
|
33
|
+
const {
|
|
34
|
+
defaultStyling = true,
|
|
35
|
+
formFieldStyle = {},
|
|
36
|
+
inputStyle = {},
|
|
37
|
+
labelStyle = {},
|
|
38
|
+
textInputStyle = {},
|
|
39
|
+
} = options;
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<FormField
|
|
43
|
+
autoCapitalize="none"
|
|
44
|
+
autoComplete="cc-csc"
|
|
45
|
+
autoCorrect={false}
|
|
46
|
+
defaultStyling={defaultStyling}
|
|
47
|
+
icon={{ type: "material", name: "lock" }}
|
|
48
|
+
keyboardType="number-pad"
|
|
49
|
+
labelStyle={labelStyle}
|
|
50
|
+
maxLength={4}
|
|
51
|
+
name="cvc"
|
|
52
|
+
placeholder="CVC"
|
|
53
|
+
styling={{ formFieldStyle, inputStyle, textInputStyle }}
|
|
54
|
+
validate={validateCvv}
|
|
55
|
+
withAnimatedText={withAnimatedText}
|
|
56
|
+
withIcon={withIcon}
|
|
57
|
+
withLabel={withLabel}
|
|
58
|
+
withPlaceholder={withPlaceholder}
|
|
59
|
+
{...otherProps}
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { StyleProp, ViewStyle, TextStyle } from "react-native";
|
|
2
|
+
|
|
3
|
+
import { FormField } from "./forms";
|
|
4
|
+
|
|
5
|
+
type CardInputOptions = {
|
|
6
|
+
options?: {
|
|
7
|
+
formFieldStyle?: StyleProp<ViewStyle>;
|
|
8
|
+
defaultStyling?: boolean;
|
|
9
|
+
inputStyle?: StyleProp<TextStyle>;
|
|
10
|
+
labelStyle?: StyleProp<TextStyle>;
|
|
11
|
+
textInputStyle?: StyleProp<TextStyle>;
|
|
12
|
+
};
|
|
13
|
+
withAnimatedText?: boolean;
|
|
14
|
+
withIcon?: boolean;
|
|
15
|
+
withLabel?: boolean;
|
|
16
|
+
withPlaceholder?: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function validateExpiry(value: string) {
|
|
20
|
+
if (!value) return "Expiry is required";
|
|
21
|
+
|
|
22
|
+
const match = /^(\d{2})\/(\d{2})$/.exec(value);
|
|
23
|
+
if (!match) return "Use MM/YY format";
|
|
24
|
+
|
|
25
|
+
const month = parseInt(match[1], 10);
|
|
26
|
+
const year = 2000 + parseInt(match[2], 10);
|
|
27
|
+
|
|
28
|
+
if (month < 1 || month > 12) return "Invalid month";
|
|
29
|
+
|
|
30
|
+
const now = new Date();
|
|
31
|
+
const expiry = new Date(year, month);
|
|
32
|
+
if (expiry <= now) return "Card expired";
|
|
33
|
+
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default function OPExpYearInput({
|
|
38
|
+
options = {},
|
|
39
|
+
withAnimatedText = true,
|
|
40
|
+
withIcon = true,
|
|
41
|
+
withLabel = false,
|
|
42
|
+
withPlaceholder = true,
|
|
43
|
+
...otherProps
|
|
44
|
+
}: CardInputOptions) {
|
|
45
|
+
const {
|
|
46
|
+
defaultStyling = true,
|
|
47
|
+
formFieldStyle = {},
|
|
48
|
+
inputStyle = {},
|
|
49
|
+
labelStyle = {},
|
|
50
|
+
textInputStyle = {},
|
|
51
|
+
} = options;
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<FormField
|
|
55
|
+
autoCapitalize="none"
|
|
56
|
+
autoComplete="cc-exp"
|
|
57
|
+
autoCorrect={false}
|
|
58
|
+
defaultStyling={defaultStyling}
|
|
59
|
+
icon={{ type: "material", name: "calendar-blank" }}
|
|
60
|
+
keyboardType="number-pad"
|
|
61
|
+
labelStyle={labelStyle}
|
|
62
|
+
maxLength={5}
|
|
63
|
+
name="exp"
|
|
64
|
+
placeholder="MM/YY"
|
|
65
|
+
styling={{ formFieldStyle, inputStyle, textInputStyle }}
|
|
66
|
+
validate={validateExpiry}
|
|
67
|
+
withAnimatedText={withAnimatedText}
|
|
68
|
+
withIcon={withIcon}
|
|
69
|
+
withLabel={withLabel}
|
|
70
|
+
withPlaceholder={withPlaceholder}
|
|
71
|
+
{...otherProps}
|
|
72
|
+
/>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { StyleProp, ViewStyle, TextStyle } from "react-native";
|
|
2
|
+
|
|
3
|
+
import { FormField } from "./forms";
|
|
4
|
+
|
|
5
|
+
type CardInputOptions = {
|
|
6
|
+
options?: {
|
|
7
|
+
formFieldStyle?: StyleProp<ViewStyle>;
|
|
8
|
+
defaultStyling?: boolean;
|
|
9
|
+
inputStyle?: StyleProp<TextStyle>;
|
|
10
|
+
labelStyle?: StyleProp<TextStyle>;
|
|
11
|
+
textInputStyle?: StyleProp<TextStyle>;
|
|
12
|
+
};
|
|
13
|
+
withAnimatedText?: boolean;
|
|
14
|
+
withIcon?: boolean;
|
|
15
|
+
withLabel?: boolean;
|
|
16
|
+
withPlaceholder?: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default function OPExpMonthInput({
|
|
20
|
+
options = {},
|
|
21
|
+
withAnimatedText = true,
|
|
22
|
+
withIcon = true,
|
|
23
|
+
withLabel = false,
|
|
24
|
+
withPlaceholder = true,
|
|
25
|
+
...otherProps
|
|
26
|
+
}: CardInputOptions) {
|
|
27
|
+
const {
|
|
28
|
+
defaultStyling = true,
|
|
29
|
+
formFieldStyle = {},
|
|
30
|
+
inputStyle = {},
|
|
31
|
+
labelStyle = {},
|
|
32
|
+
textInputStyle = {},
|
|
33
|
+
} = options;
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<FormField
|
|
37
|
+
autoCapitalize="none"
|
|
38
|
+
autoComplete="cc-exp-month"
|
|
39
|
+
autoCorrect={false}
|
|
40
|
+
defaultStyling={defaultStyling}
|
|
41
|
+
icon={{ type: "material", name: "calendar-blank" }}
|
|
42
|
+
keyboardType="number-pad"
|
|
43
|
+
labelStyle={labelStyle}
|
|
44
|
+
maxLength={2}
|
|
45
|
+
name="expMonth"
|
|
46
|
+
placeholder="MM"
|
|
47
|
+
styling={{ formFieldStyle, inputStyle, textInputStyle }}
|
|
48
|
+
withAnimatedText={withAnimatedText}
|
|
49
|
+
withIcon={withIcon}
|
|
50
|
+
withLabel={withLabel}
|
|
51
|
+
withPlaceholder={withPlaceholder}
|
|
52
|
+
{...otherProps}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { StyleProp, ViewStyle, TextStyle } from "react-native";
|
|
2
|
+
|
|
3
|
+
import { FormField } from "./forms";
|
|
4
|
+
|
|
5
|
+
type CardInputOptions = {
|
|
6
|
+
options?: {
|
|
7
|
+
formFieldStyle?: StyleProp<ViewStyle>;
|
|
8
|
+
defaultStyling?: boolean;
|
|
9
|
+
inputStyle?: StyleProp<TextStyle>;
|
|
10
|
+
labelStyle?: StyleProp<TextStyle>;
|
|
11
|
+
textInputStyle?: StyleProp<TextStyle>;
|
|
12
|
+
};
|
|
13
|
+
withAnimatedText?: boolean;
|
|
14
|
+
withIcon?: boolean;
|
|
15
|
+
withLabel?: boolean;
|
|
16
|
+
withPlaceholder?: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default function OPExpYearInput({
|
|
20
|
+
options = {},
|
|
21
|
+
withAnimatedText = true,
|
|
22
|
+
withIcon = true,
|
|
23
|
+
withLabel = false,
|
|
24
|
+
withPlaceholder = true,
|
|
25
|
+
...otherProps
|
|
26
|
+
}: CardInputOptions) {
|
|
27
|
+
const {
|
|
28
|
+
defaultStyling = true,
|
|
29
|
+
formFieldStyle = {},
|
|
30
|
+
inputStyle = {},
|
|
31
|
+
labelStyle = {},
|
|
32
|
+
textInputStyle = {},
|
|
33
|
+
} = options;
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<FormField
|
|
37
|
+
autoCapitalize="none"
|
|
38
|
+
autoComplete="cc-exp-year"
|
|
39
|
+
autoCorrect={false}
|
|
40
|
+
defaultStyling={defaultStyling}
|
|
41
|
+
icon={{ type: "material", name: "calendar-blank" }}
|
|
42
|
+
keyboardType="number-pad"
|
|
43
|
+
labelStyle={labelStyle}
|
|
44
|
+
maxLength={2}
|
|
45
|
+
name="expYear"
|
|
46
|
+
placeholder="YY"
|
|
47
|
+
styling={{ formFieldStyle, inputStyle, textInputStyle }}
|
|
48
|
+
withAnimatedText={withAnimatedText}
|
|
49
|
+
withIcon={withIcon}
|
|
50
|
+
withLabel={withLabel}
|
|
51
|
+
withPlaceholder={withPlaceholder}
|
|
52
|
+
{...otherProps}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { StyleProp, ViewStyle, TextStyle } from "react-native";
|
|
2
|
+
|
|
3
|
+
import { FormField } from "./forms";
|
|
4
|
+
|
|
5
|
+
type CardInputOptions = {
|
|
6
|
+
options?: {
|
|
7
|
+
formFieldStyle?: StyleProp<ViewStyle>;
|
|
8
|
+
defaultStyling?: boolean;
|
|
9
|
+
inputStyle?: StyleProp<TextStyle>;
|
|
10
|
+
labelStyle?: StyleProp<TextStyle>;
|
|
11
|
+
textInputStyle?: StyleProp<TextStyle>;
|
|
12
|
+
};
|
|
13
|
+
withAnimatedText?: boolean;
|
|
14
|
+
withIcon?: boolean;
|
|
15
|
+
withLabel?: boolean;
|
|
16
|
+
withPlaceholder?: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default function OPHolderNameInput({
|
|
20
|
+
options = {},
|
|
21
|
+
withAnimatedText = true,
|
|
22
|
+
withIcon = true,
|
|
23
|
+
withLabel = false,
|
|
24
|
+
withPlaceholder = true,
|
|
25
|
+
...otherProps
|
|
26
|
+
}: CardInputOptions) {
|
|
27
|
+
const {
|
|
28
|
+
defaultStyling = true,
|
|
29
|
+
formFieldStyle = {},
|
|
30
|
+
inputStyle = {},
|
|
31
|
+
labelStyle = {},
|
|
32
|
+
textInputStyle = {},
|
|
33
|
+
} = options;
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<FormField
|
|
37
|
+
autoCapitalize="words"
|
|
38
|
+
autoComplete="cc-name"
|
|
39
|
+
autoCorrect={false}
|
|
40
|
+
defaultStyling={defaultStyling}
|
|
41
|
+
icon={{ type: "material", name: "account" }}
|
|
42
|
+
labelStyle={labelStyle}
|
|
43
|
+
name="cardHolderName"
|
|
44
|
+
placeholder="Cardholder Name"
|
|
45
|
+
styling={{ formFieldStyle, inputStyle, textInputStyle }}
|
|
46
|
+
withAnimatedText={withAnimatedText}
|
|
47
|
+
withIcon={withIcon}
|
|
48
|
+
withLabel={withLabel}
|
|
49
|
+
withPlaceholder={withPlaceholder}
|
|
50
|
+
{...otherProps}
|
|
51
|
+
/>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { StyleSheet } from "react-native";
|
|
2
|
+
|
|
3
|
+
import Text from "./Text";
|
|
4
|
+
|
|
5
|
+
type ErrorMessageProps = {
|
|
6
|
+
error?: string;
|
|
7
|
+
visible: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
function ErrorMessage({ error, visible }: ErrorMessageProps): React.ReactNode {
|
|
11
|
+
if (!visible || !error) return null;
|
|
12
|
+
return <Text style={styles.error}>{error}</Text>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const styles = StyleSheet.create({
|
|
16
|
+
error: { color: "red", fontSize: 18, marginTop: 4, marginLeft: 10 },
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export default ErrorMessage;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import {
|
|
2
|
+
StyleProp,
|
|
3
|
+
StyleSheet,
|
|
4
|
+
Text,
|
|
5
|
+
TextInputProps,
|
|
6
|
+
TextStyle,
|
|
7
|
+
ViewStyle,
|
|
8
|
+
} from "react-native";
|
|
9
|
+
import { FontAwesome6, MaterialCommunityIcons } from "@expo/vector-icons";
|
|
10
|
+
|
|
11
|
+
import TextInput from "./TextInput";
|
|
12
|
+
import { formatCreditCardNumber, formatExp } from "../../utility/formatting";
|
|
13
|
+
import { useFormContext } from "../../context/FormContext";
|
|
14
|
+
|
|
15
|
+
type StylingProps = {
|
|
16
|
+
formFieldStyle?: StyleProp<ViewStyle>;
|
|
17
|
+
inputStyle?: StyleProp<ViewStyle>;
|
|
18
|
+
textInputStyle?: StyleProp<TextStyle>;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type AppFormFieldProps = TextInputProps & {
|
|
22
|
+
customStyles?: StyleProp<ViewStyle>;
|
|
23
|
+
defaultStyling?: boolean;
|
|
24
|
+
icon?:
|
|
25
|
+
| { type: "material"; name: keyof typeof MaterialCommunityIcons.glyphMap }
|
|
26
|
+
| { type: "fa6"; name: keyof typeof FontAwesome6.glyphMap }
|
|
27
|
+
| { type: "png"; name: "amex" | "discover" | "mc" | "visa" };
|
|
28
|
+
name: string;
|
|
29
|
+
placeholder?: string;
|
|
30
|
+
styling?: StylingProps;
|
|
31
|
+
labelStyle?: StyleProp<TextStyle>;
|
|
32
|
+
onValueChange?: (value: string) => void;
|
|
33
|
+
validate?: (value: string) => string | undefined;
|
|
34
|
+
withAnimatedText: boolean;
|
|
35
|
+
withIcon: boolean;
|
|
36
|
+
withLabel?: boolean;
|
|
37
|
+
withPlaceholder?: boolean;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export default function AppFormField({
|
|
41
|
+
defaultStyling,
|
|
42
|
+
name,
|
|
43
|
+
labelStyle = {},
|
|
44
|
+
placeholder,
|
|
45
|
+
validate,
|
|
46
|
+
withLabel = false,
|
|
47
|
+
...otherProps
|
|
48
|
+
}: AppFormFieldProps) {
|
|
49
|
+
const {
|
|
50
|
+
errors,
|
|
51
|
+
setFieldTouched,
|
|
52
|
+
setFieldValue,
|
|
53
|
+
setFieldError,
|
|
54
|
+
touched,
|
|
55
|
+
values,
|
|
56
|
+
} = useFormContext();
|
|
57
|
+
const hasError: boolean = !!touched[name] && !!errors[name];
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<>
|
|
61
|
+
{withLabel && (
|
|
62
|
+
<Text style={defaultStyling ? [styles.label, labelStyle] : labelStyle}>
|
|
63
|
+
{placeholder}
|
|
64
|
+
</Text>
|
|
65
|
+
)}
|
|
66
|
+
<TextInput
|
|
67
|
+
defaultStyling={defaultStyling}
|
|
68
|
+
onBlur={() => {
|
|
69
|
+
setFieldTouched(name);
|
|
70
|
+
if (validate) {
|
|
71
|
+
const errorMessage = validate(values[name]);
|
|
72
|
+
setFieldError(name, errorMessage);
|
|
73
|
+
}
|
|
74
|
+
}}
|
|
75
|
+
onChangeText={(text) => {
|
|
76
|
+
if (name === "cardNumber") text = formatCreditCardNumber(text);
|
|
77
|
+
if (name === "exp") text = formatExp(text);
|
|
78
|
+
setFieldValue(name, text);
|
|
79
|
+
|
|
80
|
+
if (otherProps.onValueChange) {
|
|
81
|
+
otherProps.onValueChange(text);
|
|
82
|
+
}
|
|
83
|
+
}}
|
|
84
|
+
hasError={hasError}
|
|
85
|
+
errorText={errors[name]}
|
|
86
|
+
placeholder={hasError ? errors[name] : placeholder}
|
|
87
|
+
value={values[name]}
|
|
88
|
+
{...otherProps}
|
|
89
|
+
/>
|
|
90
|
+
</>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const styles = StyleSheet.create({
|
|
95
|
+
label: { fontSize: 18, fontWeight: "500", marginTop: 12, paddingLeft: 6 },
|
|
96
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {
|
|
2
|
+
StyleProp,
|
|
3
|
+
StyleSheet,
|
|
4
|
+
Text,
|
|
5
|
+
TextStyle,
|
|
6
|
+
TouchableOpacity,
|
|
7
|
+
ViewStyle,
|
|
8
|
+
} from "react-native";
|
|
9
|
+
|
|
10
|
+
import { useFormContext } from "../../context/FormContext";
|
|
11
|
+
import { useFormThemeContext } from "../../context/FormThemeContext";
|
|
12
|
+
|
|
13
|
+
type SubmitButtonProps = {
|
|
14
|
+
buttonStyle?: StyleProp<ViewStyle>;
|
|
15
|
+
buttonTextStyle?: StyleProp<TextStyle>;
|
|
16
|
+
defaultStyling?: boolean;
|
|
17
|
+
onPress?: () => void;
|
|
18
|
+
title?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default function SubmitButton({
|
|
22
|
+
buttonStyle = {},
|
|
23
|
+
buttonTextStyle = {},
|
|
24
|
+
defaultStyling,
|
|
25
|
+
onPress,
|
|
26
|
+
title = "Submit",
|
|
27
|
+
}: SubmitButtonProps) {
|
|
28
|
+
const { errors, handleSubmit, isSubmitting } = useFormContext();
|
|
29
|
+
const { button, buttonText } = useFormThemeContext();
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<TouchableOpacity
|
|
33
|
+
disabled={Object.values(errors).some(Boolean) || isSubmitting}
|
|
34
|
+
style={
|
|
35
|
+
defaultStyling
|
|
36
|
+
? [
|
|
37
|
+
Object.values(errors).some(Boolean) || isSubmitting
|
|
38
|
+
? styles.buttonDisabled
|
|
39
|
+
: styles.button,
|
|
40
|
+
{ backgroundColor: button },
|
|
41
|
+
buttonStyle,
|
|
42
|
+
]
|
|
43
|
+
: buttonStyle
|
|
44
|
+
}
|
|
45
|
+
onPress={onPress || handleSubmit}
|
|
46
|
+
>
|
|
47
|
+
<Text
|
|
48
|
+
style={
|
|
49
|
+
defaultStyling
|
|
50
|
+
? [styles.text, { color: buttonText }, buttonTextStyle]
|
|
51
|
+
: buttonTextStyle
|
|
52
|
+
}
|
|
53
|
+
>
|
|
54
|
+
{title}
|
|
55
|
+
</Text>
|
|
56
|
+
</TouchableOpacity>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const styles = StyleSheet.create({
|
|
61
|
+
button: {
|
|
62
|
+
alignItems: "center",
|
|
63
|
+
borderRadius: 25,
|
|
64
|
+
justifyContent: "center",
|
|
65
|
+
marginVertical: 10,
|
|
66
|
+
padding: 15,
|
|
67
|
+
},
|
|
68
|
+
buttonDisabled: {
|
|
69
|
+
alignItems: "center",
|
|
70
|
+
borderRadius: 25,
|
|
71
|
+
justifyContent: "center",
|
|
72
|
+
marginVertical: 10,
|
|
73
|
+
opacity: 0.7,
|
|
74
|
+
padding: 15,
|
|
75
|
+
},
|
|
76
|
+
text: {
|
|
77
|
+
fontSize: 18,
|
|
78
|
+
textTransform: "uppercase",
|
|
79
|
+
fontWeight: "bold",
|
|
80
|
+
},
|
|
81
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { StyleSheet, Text, TextProps } from "react-native";
|
|
2
|
+
import Styles from "../../constants/Styles";
|
|
3
|
+
|
|
4
|
+
type AppTextProps = TextProps & {
|
|
5
|
+
children?: React.ReactNode;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
function AppText({ children, ...otherProps }: AppTextProps) {
|
|
9
|
+
return (
|
|
10
|
+
<Text style={[styles.text]} {...otherProps}>
|
|
11
|
+
{children}
|
|
12
|
+
</Text>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default AppText;
|
|
17
|
+
|
|
18
|
+
const styles = StyleSheet.create({
|
|
19
|
+
text: { ...Styles.text },
|
|
20
|
+
});
|