Skip to content

Commit

Permalink
feat(mobile): add account wallet store (#291)
Browse files Browse the repository at this point in the history
  • Loading branch information
duongdev committed Sep 9, 2024
1 parent 302526c commit 5c026b4
Show file tree
Hide file tree
Showing 26 changed files with 529 additions and 527 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"cSpell.words": [
"EXCHANGERATES",
"hono",
"openai"
"openai",
"tanstack"
]
}
12 changes: 6 additions & 6 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,24 @@
"@hono/clerk-auth": "^2.0.0",
"@hono/node-server": "^1.11.4",
"@hono/zod-validator": "^0.2.2",
"@prisma/client": "^5.16.0",
"@prisma/client": "5.19.0",
"@vercel/blob": "^0.23.4",
"got": "^14.4.1",
"hono": "^4.4.8",
"next": "^14.2.4",
"openai": "^4.52.7",
"pino": "^9.2.0",
"pino-pretty": "^11.2.1",
"react": "18.3.1",
"pino": "^9.2.0",
"prisma": "5.19.0",
"react-dom": "18.3.1",
"react": "18.3.1",
"zod-prisma-types": "^3.1.8",
"zod": "^3.23.8"
},
"devDependencies": {
"@types/node": "18.11.18",
"@types/react": "18.3.3",
"@types/react-dom": "18.3.0",
"prisma": "^5.16.0",
"typescript": "^5.5.2",
"zod-prisma-types": "^3.1.8"
"typescript": "^5.5.2"
}
}
3 changes: 2 additions & 1 deletion apps/mobile/app/(app)/budget/new-budget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ import { useRouter } from 'expo-router'
import { View } from 'react-native'

export default function CreateBudgetScreen() {
const budgetId = createId()
const router = useRouter()
const { mutateAsync } = useCreateBudget()
const { sideOffset, ...rootProps } = useModalPortalRoot()
const { setDefaultBudgetId } = useUserMetadata()

const handleCreate = async ({ isDefault, ...data }: BudgetFormValues) => {
const budgetId = createId()

if (isDefault) {
await setDefaultBudgetId(budgetId)
}
Expand Down
53 changes: 20 additions & 33 deletions apps/mobile/app/(app)/transaction/[transactionId].tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { TransactionForm } from '@/components/transaction/transaction-form'
import { sleep } from '@/lib/utils'
import { walletQueries } from '@/queries/wallet'
import {
useDeleteTransaction,
useTransaction,
Expand All @@ -14,7 +13,6 @@ import { zodResolver } from '@hookform/resolvers/zod'
import { t } from '@lingui/macro'
import { useLingui } from '@lingui/react'
import { PortalHost, useModalPortalRoot } from '@rn-primitives/portal'
import { useQueryClient } from '@tanstack/react-query'
import * as Haptics from 'expo-haptics'
import { useLocalSearchParams, useRouter } from 'expo-router'
import { useForm } from 'react-hook-form'
Expand All @@ -27,7 +25,6 @@ export default function EditRecordScreen() {
const router = useRouter()
const { mutateAsync: mutateUpdate } = useUpdateTransaction()
const { mutateAsync: mutateDelete } = useDeleteTransaction()
const queryClient = useQueryClient()
const { sideOffset, ...rootProps } = useModalPortalRoot()

const transactionForm = useForm<TransactionFormValues>({
Expand All @@ -44,20 +41,19 @@ export default function EditRecordScreen() {
})

const handleUpdate = async (values: TransactionFormValues) => {
mutateUpdate({ id: transactionId!, data: values })
.then(() => {
// TODO: remove this after the wallet store is implemented
queryClient.invalidateQueries({
queryKey: walletQueries.list._def,
})
})
.catch((error) => {
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error)
Alert.alert(
(error instanceof Error && error.message) ||
t(i18n)`An error occurred while updating the transaction`,
)
})
mutateUpdate({
id: transactionId!,
data: {
...values,
amount: values.categoryId ? values.amount : -Math.abs(values.amount),
},
}).catch((error) => {
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error)
Alert.alert(
(error instanceof Error && error.message) ||
t(i18n)`An error occurred while updating the transaction`,
)
})

await sleep(200)

Expand All @@ -81,22 +77,13 @@ export default function EditRecordScreen() {
text: t(i18n)`Delete`,
style: 'destructive',
onPress: async () => {
mutateDelete(transactionId!)
.then(() => {
// TODO: remove this after the wallet store is implemented
queryClient.invalidateQueries({
queryKey: walletQueries.list._def,
})
})
.catch((error) => {
Haptics.notificationAsync(
Haptics.NotificationFeedbackType.Error,
)
Alert.alert(
error.message ||
t(i18n)`An error occurred while deleting the transaction`,
)
})
mutateDelete(transactionId!).catch((error) => {
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error)
Alert.alert(
error.message ||
t(i18n)`An error occurred while deleting the transaction`,
)
})

router.back()
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success)
Expand Down
19 changes: 9 additions & 10 deletions apps/mobile/app/(app)/transaction/new-record.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { TransactionForm } from '@/components/transaction/transaction-form'
import { Button } from '@/components/ui/button'
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { useUserMetadata } from '@/hooks/use-user-metadata'
import { useWallets, walletQueries } from '@/queries/wallet'
import { useCreateTransaction } from '@/stores/transaction/hooks'
import { useTransactionStore } from '@/stores/transaction/store'
import { useDefaultCurrency } from '@/stores/user-settings/hooks'
import { useWalletList } from '@/stores/wallet/hooks'
import {
type TransactionFormValues,
zTransactionFormValues,
Expand All @@ -18,7 +18,6 @@ import { t } from '@lingui/macro'
import { useLingui } from '@lingui/react'
import { createId } from '@paralleldrive/cuid2'
import { PortalHost, useModalPortalRoot } from '@rn-primitives/portal'
import { useQueryClient } from '@tanstack/react-query'
import * as Haptics from 'expo-haptics'
import { useLocalSearchParams, useNavigation, useRouter } from 'expo-router'
import { CameraIcon, KeyboardIcon, Trash2Icon } from 'lucide-react-native'
Expand All @@ -36,11 +35,10 @@ import {
export default function NewRecordScreen() {
const { i18n } = useLingui()
const ref = useRef<ScrollView>(null)
const queryClient = useQueryClient()
const router = useRouter()
const { data: walletAccounts } = useWallets()
const { wallets } = useWalletList()
const defaultCurrency = useDefaultCurrency()
const defaultWallet = walletAccounts?.[0]
const defaultWallet = wallets?.[0]
const { sideOffset, ...rootProps } = useModalPortalRoot()
const [page, setPage] = useState<number>(0)
const { defaultBudgetId } = useUserMetadata()
Expand Down Expand Up @@ -119,11 +117,12 @@ export default function NewRecordScreen() {
router.back()
toast.success(t(i18n)`Transaction created`)

await mutateAsync({ id: parsedParams.id || createId(), data: values })

// TODO: remove this after the wallet store is implemented
queryClient.invalidateQueries({
queryKey: walletQueries.list._def,
await mutateAsync({
id: parsedParams.id || createId(),
data: {
...values,
amount: values.categoryId ? values.amount : -Math.abs(values.amount),
},
})
} catch (error) {
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error)
Expand Down
113 changes: 49 additions & 64 deletions apps/mobile/app/(app)/wallet/[walletId].tsx
Original file line number Diff line number Diff line change
@@ -1,63 +1,62 @@
import { Button } from '@/components/ui/button'
import { AccountForm } from '@/components/wallet/account-form'
import { deleteWallet, updateWallet } from '@/mutations/wallet'
import { transactionQueries } from '@/queries/transaction'
import { useWallets, walletQueries } from '@/queries/wallet'
import { WalletBalanceState } from '@6pm/validation'
import {
useDeleteWallet,
useUpdateWallet,
useWallet,
} from '@/stores/wallet/hooks'
import { WalletBalanceState, type WalletFormValues } from '@6pm/validation'
import { t } from '@lingui/macro'
import { useLingui } from '@lingui/react'
import { PortalHost, useModalPortalRoot } from '@rn-primitives/portal'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { useLocalSearchParams, useNavigation, useRouter } from 'expo-router'
import { Trash2Icon } from 'lucide-react-native'
import { useEffect } from 'react'
import { Alert, ScrollView, View } from 'react-native'

export default function EditAccountScreen() {
const { walletId } = useLocalSearchParams()
const { data: walletAccounts } = useWallets()
const { sideOffset, ...rootProps } = useModalPortalRoot()
const { i18n } = useLingui()
const queryClient = useQueryClient()
const router = useRouter()
const navigation = useNavigation()
const { mutateAsync: mutateUpdate } = useMutation({
mutationFn: updateWallet,
onError(error) {

const { wallet } = useWallet(walletId as string)
const { mutateAsync: mutateUpdate } = useUpdateWallet()
const { mutateAsync: mutateDelete } = useDeleteWallet()

const handleUpdateWallet = async ({ balance, ...data }: WalletFormValues) => {
if (!wallet) {
return
}

const statedBalance =
data.balanceState === WalletBalanceState.Positive
? balance
: (balance ?? 0) * -1
const adjustedBalance =
(statedBalance ?? 0) - ((wallet.balance as number) ?? 0)

mutateUpdate({
id: walletId as string,
data: {
...data,
balance: adjustedBalance,
},
}).catch((error) => {
Alert.alert(error.message)
},
onSuccess() {
router.back()
},
async onSettled() {
await Promise.all([
queryClient.invalidateQueries({
queryKey: walletQueries._def,
}),
queryClient.invalidateQueries({
queryKey: transactionQueries.all,
}),
])
},
throwOnError: true,
})
const { mutateAsync: mutateDelete } = useMutation({
mutationFn: deleteWallet,
onError(error) {
})

router.back()
}

const handleDeleteWallet = async () => {
mutateDelete(walletId as string).catch((error) => {
Alert.alert(error.message)
},
onSuccess() {
router.back()
},
async onSettled() {
await queryClient.invalidateQueries({
queryKey: walletQueries._def,
})
},
throwOnError: true,
})
})

const walletAccount = walletAccounts?.find((w) => w.id === walletId)
router.back()
}

// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
useEffect(() => {
Expand All @@ -80,7 +79,7 @@ export default function EditAccountScreen() {
{
text: t(i18n)`Delete`,
style: 'destructive',
onPress: () => mutateDelete(walletId as string),
onPress: handleDeleteWallet,
},
],
)
Expand All @@ -92,7 +91,7 @@ export default function EditAccountScreen() {
})
}, [])

if (!walletAccount) {
if (!wallet) {
return null
}

Expand All @@ -105,28 +104,14 @@ export default function EditAccountScreen() {
keyboardShouldPersistTaps="handled"
>
<AccountForm
onSubmit={({ balance, ...data }) => {
const statedBalance =
data.balanceState === WalletBalanceState.Positive
? balance
: (balance ?? 0) * -1
const adjustedBalance =
(statedBalance ?? 0) - ((walletAccount.balance as number) ?? 0)
mutateUpdate({
id: walletId as string,
data: {
...data,
balance: adjustedBalance,
},
})
}}
onSubmit={handleUpdateWallet}
defaultValues={{
name: walletAccount.name,
preferredCurrency: walletAccount.preferredCurrency,
balance: walletAccount.balance,
icon: walletAccount.icon ?? 'CreditCard',
description: walletAccount.description ?? '',
lastDigits: walletAccount.lastDigits ?? '',
name: wallet.name,
preferredCurrency: wallet.preferredCurrency,
balance: wallet.balance,
icon: wallet.icon ?? 'CreditCard',
description: wallet.description ?? '',
lastDigits: wallet.lastDigits ?? '',
}}
sideOffset={sideOffset}
/>
Expand Down
4 changes: 2 additions & 2 deletions apps/mobile/app/(app)/wallet/accounts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Text } from '@/components/ui/text'
import { WalletAccountItem } from '@/components/wallet/wallet-account-item'
import { useUserEntitlements } from '@/hooks/use-purchases'
import { ENTILEMENT_LIMIT } from '@/lib/constaints'
import { useWallets } from '@/queries/wallet'
import { useWalletList } from '@/stores/wallet/hooks'
import { t } from '@lingui/macro'
import { useLingui } from '@lingui/react'
import { Link } from 'expo-router'
Expand All @@ -16,7 +16,7 @@ import { FlatList } from 'react-native'

export default function WalletAccountsScreen() {
const { i18n } = useLingui()
const { data: walletAccounts, isLoading, refetch } = useWallets()
const { wallets: walletAccounts, isLoading, refetch } = useWalletList()
const router = useRouter()
const navigation = useNavigation()
const { entilement } = useUserEntitlements()
Expand Down
Loading

0 comments on commit 5c026b4

Please sign in to comment.