From facfdc4ea76b7714f76db3a2ddc52a14fe024965 Mon Sep 17 00:00:00 2001 From: privateducksheep Date: Thu, 8 Jan 2026 17:57:28 +0800 Subject: [PATCH 1/2] Frontend: wallet, receive QR, pay QR scan (demo-ready) --- apps/mobile/app.json | 3 +- apps/mobile/app/(tabs)/pay.tsx | 123 +++++++++ apps/mobile/app/(tabs)/receipt.tsx | 38 +++ apps/mobile/app/(tabs)/receive.tsx | 32 +++ apps/mobile/app/(tabs)/wallet.tsx | 70 +++++ apps/mobile/package-lock.json | 424 ++++++++++++++++++++++++++++- apps/mobile/package.json | 11 +- apps/mobile/src/api.ts | 32 +++ apps/mobile/src/config.ts | 2 + 9 files changed, 728 insertions(+), 7 deletions(-) create mode 100644 apps/mobile/app/(tabs)/pay.tsx create mode 100644 apps/mobile/app/(tabs)/receipt.tsx create mode 100644 apps/mobile/app/(tabs)/receive.tsx create mode 100644 apps/mobile/app/(tabs)/wallet.tsx create mode 100644 apps/mobile/src/api.ts create mode 100644 apps/mobile/src/config.ts diff --git a/apps/mobile/app.json b/apps/mobile/app.json index 80f0da4..57ed0fc 100644 --- a/apps/mobile/app.json +++ b/apps/mobile/app.json @@ -38,7 +38,8 @@ "backgroundColor": "#000000" } } - ] + ], + "expo-barcode-scanner" ], "experiments": { "typedRoutes": true, diff --git a/apps/mobile/app/(tabs)/pay.tsx b/apps/mobile/app/(tabs)/pay.tsx new file mode 100644 index 0000000..5d960ed --- /dev/null +++ b/apps/mobile/app/(tabs)/pay.tsx @@ -0,0 +1,123 @@ +import React, { useEffect, useState } from "react"; +import { View, Text, TextInput, Button, Alert } from "react-native"; +import { CameraView, useCameraPermissions } from "expo-camera"; +import { router } from "expo-router"; +import { apiPost } from "../../src/api"; + +export default function PayScreen() { + const [permission, requestPermission] = useCameraPermissions(); + const [scanned, setScanned] = useState(false); + const [toAddress, setToAddress] = useState(""); + const [amount, setAmount] = useState(""); + + // TODO later: replace with real login token + const token: string | undefined = undefined; + + useEffect(() => { + if (!permission) requestPermission(); + }, [permission]); + + function parseQR(data: string): string { + try { + const obj = JSON.parse(data); + if (obj?.address && typeof obj.address === "string") return obj.address; + } catch {} + return data.trim(); + } + + async function sendPayment() { + if (!toAddress) return Alert.alert("Scan a QR first"); + const n = Number(amount); + if (!amount || Number.isNaN(n) || n <= 0) return Alert.alert("Invalid amount"); + + try { + const res = await apiPost( + "/tx/send", + { to_address: toAddress, amount_xrp: n }, + token + ); + + router.push({ + pathname: "/receipt", + params: { status: "success", to: toAddress, amount: String(n), txHash: res?.tx_hash ?? "" }, + }); + } catch (e: any) { + router.push({ + pathname: "/receipt", + params: { status: "fail", to: toAddress, amount: String(n), error: e?.message ?? String(e) }, + }); + } + } + + if (!permission) { + return ( + + Requesting camera permission... + + ); + } + + if (!permission.granted) { + return ( + + Camera permission denied. +