diff --git a/apps/mobile/app/(app)/_layout.tsx b/apps/mobile/app/(app)/_layout.tsx
index ff5f4cc5..5863f89e 100644
--- a/apps/mobile/app/(app)/_layout.tsx
+++ b/apps/mobile/app/(app)/_layout.tsx
@@ -55,6 +55,13 @@ export default function AuthenticatedLayout() {
headerShown: false,
}}
/>
+
()
+ const { data: transaction } = useTransactionDetail(transactionId!)
+ const router = useRouter()
+ const queryClient = useQueryClient()
+ const { mutateAsync } = useMutation({
+ mutationFn: updateTransaction,
+ onError(error) {
+ Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error)
+ Alert.alert(error.message)
+ },
+ onSuccess() {
+ Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success)
+ router.back()
+ },
+ async onSettled() {
+ await queryClient.invalidateQueries({
+ queryKey: transactionQueries.all,
+ })
+ await queryClient.invalidateQueries({
+ queryKey: walletQueries.list._def,
+ })
+ },
+ })
+ const { mutateAsync: mutateDelete } = useMutation({
+ mutationFn: deleteTransaction,
+ onMutate() {
+ router.back()
+ },
+ onError(error) {
+ Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error)
+ Alert.alert(error.message)
+ },
+ onSuccess() {
+ Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success)
+ },
+ async onSettled() {
+ await queryClient.invalidateQueries({
+ queryKey: transactionQueries.all,
+ })
+ await queryClient.invalidateQueries({
+ queryKey: walletQueries.list._def,
+ })
+ },
+ throwOnError: true,
+ })
+
+ function handleDelete() {
+ Haptics.selectionAsync()
+ Alert.alert(
+ t(
+ i18n,
+ )`This will delete the transaction. Are you sure you want to continue?`,
+ '',
+ [
+ {
+ text: t(i18n)`Cancel`,
+ style: 'cancel',
+ },
+ {
+ text: t(i18n)`Delete`,
+ style: 'destructive',
+ onPress: () => mutateDelete(transactionId!),
+ },
+ ],
+ )
+ }
+
+ if (!transaction) {
+ return (
+
+
+
+ )
+ }
+
+ 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/components/transaction/transaction-form.tsx b/apps/mobile/components/transaction/transaction-form.tsx
index a79e2185..99c2b450 100644
--- a/apps/mobile/components/transaction/transaction-form.tsx
+++ b/apps/mobile/components/transaction/transaction-form.tsx
@@ -5,7 +5,7 @@ import {
import { zodResolver } from '@hookform/resolvers/zod'
import { t } from '@lingui/macro'
import { useLingui } from '@lingui/react'
-import { LandPlot, XIcon } from 'lucide-react-native'
+import { LandPlot, Trash2Icon, XIcon } from 'lucide-react-native'
import { Controller, FormProvider, useForm } from 'react-hook-form'
import { ScrollView, View } from 'react-native'
import Animated, {
@@ -26,12 +26,14 @@ type TransactionFormProps = {
onSubmit: (data: TransactionFormValues) => void
defaultValues?: Partial
onCancel?: () => void
+ onDelete?: () => void
}
export const TransactionForm = ({
onSubmit,
defaultValues,
onCancel,
+ onDelete,
}: TransactionFormProps) => {
const { i18n } = useLingui()
@@ -65,9 +67,16 @@ export const TransactionForm = ({
>
-
+
+ {onDelete && (
+
+ )}
+
+
@@ -108,7 +117,11 @@ export const TransactionForm = ({
{t(i18n)`Save`}
diff --git a/apps/mobile/mutations/transaction.ts b/apps/mobile/mutations/transaction.ts
index 622821fe..8655c360 100644
--- a/apps/mobile/mutations/transaction.ts
+++ b/apps/mobile/mutations/transaction.ts
@@ -23,3 +23,39 @@ export async function createTransaction(data: TransactionFormValues) {
return result
}
+
+export async function updateTransaction({
+ id,
+ data,
+}: {
+ id: string
+ data: TransactionFormValues
+}) {
+ const hc = await getHonoClient()
+ const result = await hc.v1.transactions[':transactionId'].$put({
+ param: { transactionId: id },
+ json: {
+ ...data,
+ amount: -data.amount,
+ },
+ })
+
+ if (result.ok) {
+ const transaction = await result.json()
+ return TransactionSchema.merge(
+ z.object({
+ // override Decimal type with number
+ amount: z.number({ coerce: true }),
+ }),
+ ).parse(transaction)
+ }
+
+ return result
+}
+
+export async function deleteTransaction(id: string) {
+ const hc = await getHonoClient()
+ await hc.v1.transactions[':transactionId'].$delete({
+ param: { transactionId: id },
+ })
+}
diff --git a/apps/mobile/queries/transaction.ts b/apps/mobile/queries/transaction.ts
index c0652bdc..b6b8d3be 100644
--- a/apps/mobile/queries/transaction.ts
+++ b/apps/mobile/queries/transaction.ts
@@ -1,7 +1,11 @@
import { getHonoClient } from '@/lib/client'
import { TransactionPopulatedSchema } from '@6pm/validation'
// import { createQueryKeys } from '@lukemorales/query-key-factory'
-import { useInfiniteQuery } from '@tanstack/react-query'
+import {
+ useInfiniteQuery,
+ useQuery,
+ useQueryClient,
+} from '@tanstack/react-query'
export type TransactionFilters = {
walletAccountId?: string
@@ -42,13 +46,36 @@ export const transactionQueries = {
}
},
}),
+ detail: (transactionId: string) => ({
+ queryKey: ['transaction', { transactionId }],
+ queryFn: async () => {
+ const hc = await getHonoClient()
+ const res = await hc.v1.transactions[':transactionId'].$get({
+ param: { transactionId },
+ })
+ if (!res.ok) {
+ throw new Error(await res.text())
+ }
+
+ const transaction = await res.json()
+ return TransactionPopulatedSchema.parse(transaction)
+ },
+ }),
}
export function useTransactions(filters: TransactionFilters) {
+ const queryClient = useQueryClient()
return useInfiniteQuery({
queryKey: transactionQueries.list(filters).queryKey,
queryFn: async ({ pageParam = filters.before?.toString() }) => {
- return transactionQueries.list(filters).queryFn(pageParam)
+ const result = await transactionQueries.list(filters).queryFn(pageParam)
+ result.transactions?.forEach((transaction) => {
+ queryClient.setQueryData(
+ transactionQueries.detail(transaction.id).queryKey,
+ transaction,
+ )
+ })
+ return result
},
initialPageParam: '',
getNextPageParam: (lastPage) => {
@@ -56,3 +83,9 @@ export function useTransactions(filters: TransactionFilters) {
},
})
}
+
+export function useTransactionDetail(transactionId: string) {
+ return useQuery({
+ ...transactionQueries.detail(transactionId),
+ })
+}