Skip to content

Commit

Permalink
feat(mobile): add account wallet store
Browse files Browse the repository at this point in the history
  • Loading branch information
duongdev committed Sep 9, 2024
1 parent b9585b6 commit 31ffdca
Show file tree
Hide file tree
Showing 8 changed files with 10,984 additions and 5,978 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"
]
}
88 changes: 40 additions & 48 deletions apps/mobile/app/(app)/wallet/[walletId].tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
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 { deleteWallet } from '@/mutations/wallet'
import { walletQueries } from '@/queries/wallet'
import { 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'
Expand All @@ -15,32 +15,15 @@ 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) {
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 { wallet } = useWallet(walletId as string)
const { mutateAsync: mutateUpdate } = useUpdateWallet()

const { mutateAsync: mutateDelete } = useMutation({
mutationFn: deleteWallet,
onError(error) {
Expand All @@ -57,7 +40,30 @@ export default function EditAccountScreen() {
throwOnError: true,
})

const walletAccount = walletAccounts?.find((w) => w.id === walletId)
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)
})

router.back()
}

// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
useEffect(() => {
Expand Down Expand Up @@ -92,7 +98,7 @@ export default function EditAccountScreen() {
})
}, [])

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

Expand All @@ -105,28 +111,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
16 changes: 13 additions & 3 deletions apps/mobile/stores/transaction/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -174,11 +174,20 @@ export function useCreateTransaction() {
data,
}: { id: string; data: TransactionFormValues }) => {
const hc = await getHonoClient()
const category = data.categoryId ? categoriesDict[data.categoryId] : null
const categoryType = category?.type

const amount = category
? categoryType === 'INCOME'
? Math.abs(data.amount)
: -Math.abs(data.amount)
: data.amount

const result = await hc.v1.transactions.$post({
json: {
...data,
id,
amount: -data.amount,
amount,
},
})

Expand All @@ -203,10 +212,11 @@ export function useCreateTransaction() {
const category = data.categoryId ? categoriesDict[data.categoryId] : null
const categoryType = category?.type

const amount =
categoryType === 'INCOME'
const amount = category
? categoryType === 'INCOME'
? Math.abs(data.amount)
: -Math.abs(data.amount)
: data.amount

const transaction: TransactionPopulated = {
id,
Expand Down
128 changes: 128 additions & 0 deletions apps/mobile/stores/wallet/hooks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { getHonoClient } from '@/lib/client'
import type { WalletFormValues } from '@6pm/validation'
import { createId } from '@paralleldrive/cuid2'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { keyBy } from 'lodash-es'
import { useMemo } from 'react'
import type { StoreHookQueryOptions } from '../core/stores'
import { useCreateTransaction } from '../transaction/hooks'
import { walletQueries } from './queries'
import { useWalletStore } from './store'

export const useWalletListQueryOptions = (
queryOptions?: StoreHookQueryOptions,
) => {
const wallets = useWalletStore().wallets
const setWalletsState = useWalletStore((state) => state.setWallets)
return {
...walletQueries.all({ setWalletsState }),
initialData: wallets?.length > 0 ? wallets : undefined,
...queryOptions,
}
}

export const useWalletList = (queryOptions?: StoreHookQueryOptions) => {
const wallets = useWalletStore().wallets
const queryOpts = useWalletListQueryOptions(queryOptions)

const query = useQuery(queryOpts)

const walletsDict = useMemo(() => keyBy(wallets, 'id'), [wallets])

return {
...query,
wallets,
walletsDict,
}
}

export const useWallet = (walletId: string) => {
const wallets = useWalletStore().wallets
const wallet = useMemo(
() => wallets.find((wallet) => wallet.id === walletId) || null,
[wallets, walletId],
)

return { wallet }
}

export const useUpdateWallet = () => {
const updateWalletInStore = useWalletStore((state) => state.updateWallet)
const { walletsDict } = useWalletList()
const { mutateAsync: mutateCreateTransaction } = useCreateTransaction()
const queryClient = useQueryClient()

const mutation = useMutation(
{
mutationFn: async ({
id: walletId,
data,
}: {
id: string
data: WalletFormValues
}) => {
const { balance, ...walletData } = data
const hc = await getHonoClient()
const wallet = walletsDict[walletId]

if (!wallet) {
return
}

// Update wallet info and create adjust balance transaction
const promises = [
hc.v1.wallets[':walletId'].$put({
param: { walletId },
json: walletData,
}),
]

if (balance) {
promises.push(
mutateCreateTransaction({
id: createId(),
data: {
amount: balance ?? 0,
walletAccountId: wallet.id,
currency: wallet.preferredCurrency,
date: new Date(),
note: 'Adjust balance',
},
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
}) as any,
)
}

const [result] = await Promise.all(promises)

return result
},
onMutate: async ({ id, data: { balance, ...data } }) => {
let wallet = walletsDict[id]
if (!wallet) {
return
}

const newBallance = (balance ?? 0) + (wallet.balance ?? 0)

wallet = {
...wallet,
...data,
balance: newBallance,
updatedAt: new Date(),
}

updateWalletInStore(wallet)

return wallet
},
},
queryClient,
)

return mutation
}

// export const useCreateWallet = () => {}

// export const useDeleteWallet = () => {}
40 changes: 40 additions & 0 deletions apps/mobile/stores/wallet/queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { getHonoClient } from '@/lib/client'
import {
type WalletAccountWithBalance,
WalletAccountWithBalanceSchema,
} from '@6pm/validation'
import { createQueryKeys } from '@lukemorales/query-key-factory'
import { z } from 'zod'

export const walletQueries = createQueryKeys('wallet', {
all: ({
setWalletsState,
}: {
setWalletsState: (wallets: WalletAccountWithBalance[]) => void
}) => ({
queryKey: [{}],
queryFn: async () => {
const hc = await getHonoClient()
const res = await hc.v1.wallets.$get()

if (!res.ok) {
throw new Error(await res.text())
}

try {
const items = await res.json()
const wallets = items.map((item) =>
WalletAccountWithBalanceSchema.extend({
id: z.string().cuid2(),
}).parse(item),
)

setWalletsState(wallets)
return wallets
} catch (error) {
// biome-ignore lint/suspicious/noConsoleLog: <explanation>
console.log(error)
}
},
}),
})
Loading

0 comments on commit 31ffdca

Please sign in to comment.