Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 29 additions & 66 deletions dapps/pos-app/app/amount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,9 @@ import {
getCurrency,
} from "@/utils/currency";
import { router } from "expo-router";
import { Controller, useForm, useWatch } from "react-hook-form";
import { useCallback, useState } from "react";
import { Platform, StyleSheet, View } from "react-native";

interface FormData {
amount: string;
}

const formatAmount = (amount: string) => {
if (!amount.includes(".")) {
return `${amount}.00`;
Expand All @@ -36,31 +32,42 @@ const formatAmount = (amount: string) => {
return `${whole}.${paddedDecimal}`;
};

const getNextAmount = (prev: string, key: string) => {
if (key === "erase") return prev.slice(0, -1);
if (key === ".") {
if (prev.includes(".")) return prev; // Don't add multiple decimal separators
return prev === "" ? "0." : `${prev}.`;
}
// Limit to 2 decimal places
const decimalPart = prev.split(".")[1];
if (decimalPart !== undefined && decimalPart.length >= 2) return prev;
const next = prev === "0" ? key : prev + key;
return exceedsU64Max(next) ? prev : next;
};

const isValidAmount = (amount: string) => !!amount && Number(amount) !== 0;

export default function AmountScreen() {
const Theme = useTheme();
const testMode = useSettingsStore((state) => state.testMode);
const isTestPayment = testMode;
const isTablet = useIsTablet();
const currencyCode = useSettingsStore((state) => state.currency);
const currency = getCurrency(currencyCode);
const {
control,
handleSubmit,
formState: { isValid },
} = useForm<FormData>({
defaultValues: {
amount: "",
},
});
const watchAmount = useWatch({ control, name: "amount" });
const [amount, setAmount] = useState("");
const isValid = isValidAmount(amount);

const onSubmit = ({ amount }: FormData) => {
const formattedAmount = formatAmount(amount);
// Stable identity so the memoized NumericKeyboard doesn't re-render per key.
const handleKeyPress = useCallback((key: string) => {
setAmount((prev) => getNextAmount(prev, key));
}, []);

const onSubmit = () => {
if (!isValid) return;
router.push({
pathname: "/scan",
params: {
amount: formattedAmount,
amount: formatAmount(amount),
},
});
};
Expand All @@ -76,68 +83,24 @@ export default function AmountScreen() {
>
<BigAmountInput
testID="amount-display"
value={watchAmount}
value={amount}
currency={currency.symbol}
symbolPosition={currency.symbolPosition}
size={isTablet ? "lg" : "md"}
/>
</View>
<Controller
control={control}
name="amount"
rules={{
validate: (value) => {
if (
!value ||
value === "0" ||
value === "" ||
Number(value) === 0
) {
return "Amount is required";
}
return true;
},
}}
render={({ field: { onChange, value: prev } }) => (
<NumericKeyboard
onKeyPress={(key) => {
let newDisplay;
if (key === "erase") {
newDisplay = prev?.slice(0, -1) || "";
onChange?.(newDisplay);
} else if (key === ".") {
if (prev.includes(".")) return; // Don't add multiple decimal separators
if (prev === "") {
newDisplay = "0.";
} else {
newDisplay = prev + ".";
}
onChange?.(newDisplay);
} else {
// Limit to 2 decimal places
if (prev.includes(".")) {
const decimalPart = prev.split(".")[1] || "";
if (decimalPart.length >= 2) return;
}
const newDisplay = prev === "0" ? key : prev + key;
if (exceedsU64Max(newDisplay)) return;
onChange?.(newDisplay);
}
}}
/>
)}
/>
<NumericKeyboard onKeyPress={handleKeyPress} />
<Button
type="accent"
variant="primary"
testID="charge-button"
onPress={handleSubmit(onSubmit)}
onPress={onSubmit}
disabled={!isValid}
size={isTablet ? "lg" : "md"}
style={[styles.button, isTablet && styles.buttonTablet]}
>
{isValid
? `Charge ${formatAmountWithSymbol(formatAmount(watchAmount), currency)}`
? `Charge ${formatAmountWithSymbol(formatAmount(amount), currency)}`
: "Enter amount"}
</Button>
</View>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { CharacterItem } from "../utils/getCharactersArray";
import { memo, useEffect, useRef } from "react";
import { StyleSheet } from "react-native";
import { StyleSheet, Text } from "react-native";
import Animated, {
Easing,
useAnimatedStyle,
Expand Down Expand Up @@ -113,9 +113,9 @@ function AnimatedCharacterComponent({
animatedStyle,
]}
>
<Animated.Text style={[styles.text, { color: textColor, fontSize }]}>
<Text style={[styles.text, { color: textColor, fontSize }]}>
{item.char}
</Animated.Text>
</Text>
</Animated.View>
</Animated.View>
);
Expand Down
31 changes: 23 additions & 8 deletions dapps/pos-app/components/big-amount-input/utils/formatAmount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,23 @@ export type FormatAmountOptions = {

const DEFAULT_LOCALE: SupportedLocale = "en-US";

// Separators are fixed per locale, and constructing Intl.NumberFormat is slow on
// Android Hermes (ICU locale resolution over JNI), so resolve each locale once
// instead of on every keypress.
const decimalSeparators = new Map<SupportedLocale, string>();
const groupSeparators = new Map<SupportedLocale, string>();

export function getDecimalSeparator(
locale: SupportedLocale = DEFAULT_LOCALE,
): string {
const numberWithDecimal = 1.1;
const formatted = new Intl.NumberFormat(locale).format(numberWithDecimal);
return formatted.charAt(1);
let separator = decimalSeparators.get(locale);
if (separator === undefined) {
const numberWithDecimal = 1.1;
const formatted = new Intl.NumberFormat(locale).format(numberWithDecimal);
separator = formatted.charAt(1);
decimalSeparators.set(locale, separator);
}
return separator;
}

export function getDefaultLocale(): SupportedLocale {
Expand All @@ -32,11 +43,15 @@ export function parseRawValue(rawValue: string): number {
}

function getGroupSeparator(locale: SupportedLocale): string {
// Avoid formatToParts — not supported in Hermes
const formatted = new Intl.NumberFormat(locale).format(10000);
// Strip all digits — what remains is the group separator (e.g. "," or ".")
const sep = formatted.replace(/\d/g, "");
return sep.charAt(0) || ",";
let separator = groupSeparators.get(locale);
if (separator === undefined) {
// Avoid formatToParts — not supported in Hermes
const formatted = new Intl.NumberFormat(locale).format(10000);
// Strip all digits — what remains is the group separator (e.g. "," or ".")
separator = formatted.replace(/\d/g, "").charAt(0) || ",";
groupSeparators.set(locale, separator);
}
return separator;
}

function addThousandsSeparators(integerStr: string, separator: string): string {
Expand Down
21 changes: 9 additions & 12 deletions dapps/pos-app/components/numeric-keyboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,29 @@ export interface NumericKeyboardProps {
style?: StyleProp<ViewStyle>;
}

const KEYS = [
["1", "2", "3"],
["4", "5", "6"],
["7", "8", "9"],
[".", "0", "erase"],
];

function NumericKeyboardBase({ onKeyPress, style }: NumericKeyboardProps) {
const Theme = useTheme();
const isTablet = useIsTablet();
const [assets] = useAssets([require("@/assets/images/backspace.png")]);
const keys = [
["1", "2", "3"],
["4", "5", "6"],
["7", "8", "9"],
[".", "0", "erase"],
];

const handlePress = (key: string) => {
onKeyPress(key);
};

return (
<View style={[styles.container, isTablet && styles.containerTablet, style]}>
{keys.map((row, rowIndex) => (
{KEYS.map((row, rowIndex) => (
<View
key={`row-${rowIndex}`}
style={[styles.row, isTablet && styles.rowTablet]}
>
{row.map((key) => (
<Pressable
key={key}
onPress={() => handlePress(key)}
onPress={() => onKeyPress(key)}
testID={key === "." ? "key-decimal" : `key-${key}`}
accessibilityRole="button"
accessibilityLabel={key === "erase" ? "Backspace" : key}
Expand Down
17 changes: 0 additions & 17 deletions dapps/pos-app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion dapps/pos-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
"lint": "expo lint",
"test": "jest"
},
"reanimated": {
"staticFeatureFlags": {
"ANDROID_SYNCHRONOUSLY_UPDATE_UI_PROPS": true
}
},
"dependencies": {
"@rnrepo/expo-config-plugin": "0.3.6",
"@sentry/react-native": "8.23.0",
Expand Down Expand Up @@ -53,7 +58,6 @@
"pressto": "0.6.1",
"react": "19.2.3",
"react-dom": "19.2.3",
"react-hook-form": "7.71.2",
"react-native": "0.85.3",
"react-native-device-info": "14.1.1",
"react-native-gesture-handler": "2.31.2",
Expand Down
Loading