diff --git a/apps/mobile/app/(app)/(tabs)/_layout.tsx b/apps/mobile/app/(app)/(tabs)/_layout.tsx
index f534b150..937b7fa0 100644
--- a/apps/mobile/app/(app)/(tabs)/_layout.tsx
+++ b/apps/mobile/app/(app)/(tabs)/_layout.tsx
@@ -1,12 +1,14 @@
+import { Button } from '@/components/ui/button'
import { useColorScheme } from '@/hooks/useColorScheme'
import { theme } from '@/lib/theme'
import { t } from '@lingui/macro'
import { useLingui } from '@lingui/react'
-import { Tabs } from 'expo-router'
+import { Link, Tabs } from 'expo-router'
import {
+ // BarChartBigIcon,
CogIcon,
LandPlotIcon,
- ScanTextIcon,
+ PlusIcon,
WalletIcon,
} from 'lucide-react-native'
@@ -47,16 +49,23 @@ export default function TabLayout() {
headerTitle: t(i18n)`Budgets`,
tabBarShowLabel: false,
tabBarIcon: ({ color }) => ,
+ headerRight: () => (
+
+
+
+ ),
}}
/>
- ,
+ tabBarIcon: ({ color }) => ,
}}
- />
+ /> */}
({
+ resolver: zodResolver(zTransactionFormValues),
+ defaultValues: {
+ walletAccountId: transaction?.walletAccountId,
+ currency: transaction?.currency,
+ amount: Math.abs(transaction?.amount ?? 0),
+ date: transaction?.date,
+ note: transaction?.note ?? '',
+ budgetId: transaction?.budgetId ?? undefined,
+ categoryId: transaction?.categoryId ?? undefined,
+ },
+ })
+
const { mutateAsync } = useMutation({
mutationFn: updateTransaction,
onError(error) {
@@ -89,17 +109,9 @@ export default function EditRecordScreen() {
return (
mutateAsync({ id: transaction.id, data: values })}
onCancel={router.back}
- defaultValues={{
- walletAccountId: transaction.walletAccountId,
- currency: transaction.currency,
- amount: Math.abs(transaction.amount),
- date: transaction.date,
- note: transaction.note ?? '',
- budgetId: transaction.budgetId ?? undefined,
- categoryId: transaction.categoryId ?? undefined,
- }}
onDelete={handleDelete}
/>
)
diff --git a/apps/mobile/app/(app)/transaction/new-record.tsx b/apps/mobile/app/(app)/transaction/new-record.tsx
index ab3e1861..641a13f9 100644
--- a/apps/mobile/app/(app)/transaction/new-record.tsx
+++ b/apps/mobile/app/(app)/transaction/new-record.tsx
@@ -1,24 +1,50 @@
import { toast } from '@/components/common/toast'
+import { Scanner } from '@/components/transaction/scanner'
import { TransactionForm } from '@/components/transaction/transaction-form'
import { createTransaction } from '@/mutations/transaction'
import { transactionQueries } from '@/queries/transaction'
import { useWallets, walletQueries } from '@/queries/wallet'
-import { zUpdateTransaction } from '@6pm/validation'
+import {
+ type TransactionFormValues,
+ zTransactionFormValues,
+ zUpdateTransaction,
+} from '@6pm/validation'
+import { zodResolver } from '@hookform/resolvers/zod'
import { t } from '@lingui/macro'
import { useLingui } from '@lingui/react'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import * as Haptics from 'expo-haptics'
import { useLocalSearchParams, useRouter } from 'expo-router'
import { LoaderIcon } from 'lucide-react-native'
+import { useRef } from 'react'
+import { useForm } from 'react-hook-form'
import { Alert, View } from 'react-native'
+import PagerView from 'react-native-pager-view'
export default function NewRecordScreen() {
- const router = useRouter()
- const params = useLocalSearchParams()
- const defaultValues = zUpdateTransaction.parse(params)
- const { data: walletAccounts } = useWallets()
const { i18n } = useLingui()
+ const ref = useRef(null)
const queryClient = useQueryClient()
+ const router = useRouter()
+ const { data: walletAccounts } = useWallets()
+ const defaultWallet = walletAccounts?.[0]
+
+ const params = useLocalSearchParams()
+ const parsedParams = zUpdateTransaction.parse(params)
+ const defaultValues = {
+ date: new Date(),
+ amount: 0,
+ currency: 'USD',
+ note: '',
+ walletAccountId: defaultWallet?.id,
+ ...parsedParams,
+ }
+
+ const transactionForm = useForm({
+ resolver: zodResolver(zTransactionFormValues),
+ defaultValues,
+ })
+
const { mutateAsync } = useMutation({
mutationFn: createTransaction,
onError(error) {
@@ -42,8 +68,6 @@ export default function NewRecordScreen() {
},
})
- const defaultWallet = walletAccounts?.[0]
-
if (!defaultWallet) {
return (
@@ -53,14 +77,39 @@ export default function NewRecordScreen() {
}
return (
-
+
+
+ {
+ ref.current?.setPage(1)
+ }}
+ />
+ ref.current?.setScrollEnabled(false)}
+ onScanResult={(result) => {
+ transactionForm.reset(
+ {
+ ...defaultValues,
+ ...result,
+ },
+ {
+ keepDefaultValues: false,
+ },
+ )
+ ref.current?.setScrollEnabled(true)
+ ref.current?.setPage(0)
+ }}
+ />
+
+
)
}
diff --git a/apps/mobile/app/(app)/(tabs)/scanner.tsx b/apps/mobile/components/transaction/scanner.tsx
similarity index 88%
rename from apps/mobile/app/(app)/(tabs)/scanner.tsx
rename to apps/mobile/components/transaction/scanner.tsx
index 1a150481..7a4a66b3 100644
--- a/apps/mobile/app/(app)/(tabs)/scanner.tsx
+++ b/apps/mobile/components/transaction/scanner.tsx
@@ -4,6 +4,7 @@ import { ScanningOverlay } from '@/components/scanner/scanning-overlay'
import { Button } from '@/components/ui/button'
import { Text } from '@/components/ui/text'
import { getAITransactionData } from '@/mutations/transaction'
+import type { UpdateTransaction } from '@6pm/validation'
import { t } from '@lingui/macro'
import { useLingui } from '@lingui/react'
import { useMutation } from '@tanstack/react-query'
@@ -11,7 +12,6 @@ import { type CameraType, CameraView, useCameraPermissions } from 'expo-camera'
import * as Haptics from 'expo-haptics'
import { SaveFormat, manipulateAsync } from 'expo-image-manipulator'
import * as ImagePicker from 'expo-image-picker'
-import { useRouter } from 'expo-router'
import {
CameraIcon,
ImagesIcon,
@@ -22,6 +22,7 @@ import { cssInterop } from 'nativewind'
import { useRef, useState } from 'react'
import { Alert } from 'react-native'
import { ImageBackground, View } from 'react-native'
+import { useSafeAreaInsets } from 'react-native-safe-area-context'
cssInterop(CameraView, {
className: {
@@ -29,16 +30,24 @@ cssInterop(CameraView, {
},
})
-export default function ScannerScreen() {
+type ScannerProps = {
+ onScanStart?: () => void
+ onScanResult: (result: UpdateTransaction) => void
+}
+
+export function Scanner({ onScanStart, onScanResult }: ScannerProps) {
const camera = useRef(null)
- const router = useRouter()
const [facing, setFacing] = useState('back')
const [permission, requestPermission] = useCameraPermissions()
const [imageUri, setImageUri] = useState(null)
const { i18n } = useLingui()
+ const { bottom } = useSafeAreaInsets()
const { mutateAsync } = useMutation({
mutationFn: getAITransactionData,
+ onMutate() {
+ onScanStart?.()
+ },
onError(error) {
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error)
Alert.alert(error.message ?? t(i18n)`Cannot extract transaction data`)
@@ -46,11 +55,7 @@ export default function ScannerScreen() {
},
onSuccess(result) {
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success)
- router.push({
- pathname: '/transaction/new-record',
- // biome-ignore lint/suspicious/noExplicitAny:
- params: result as any,
- })
+ onScanResult(result)
setImageUri(null)
},
})
@@ -131,6 +136,7 @@ export default function ScannerScreen() {
{/* */}
@@ -140,7 +146,7 @@ export default function ScannerScreen() {