diff --git a/apps/mobile/app/(app)/(tabs)/settings.tsx b/apps/mobile/app/(app)/(tabs)/settings.tsx
index 35af9c25..71aa9188 100644
--- a/apps/mobile/app/(app)/(tabs)/settings.tsx
+++ b/apps/mobile/app/(app)/(tabs)/settings.tsx
@@ -82,7 +82,7 @@ export default function SettingsScreen() {
{t(i18n)`General`}
-
+
diff --git a/apps/mobile/components/auth/auth-social.tsx b/apps/mobile/components/auth/auth-social.tsx
index 80ca4fd8..9461f604 100644
--- a/apps/mobile/components/auth/auth-social.tsx
+++ b/apps/mobile/components/auth/auth-social.tsx
@@ -1,4 +1,4 @@
-import { useCreateUserMutation } from '@/mutations/user'
+import { createUser } from '@/mutations/user'
import { useOAuth } from '@clerk/clerk-expo'
import { t } from '@lingui/macro'
import { useLingui } from '@lingui/react'
@@ -16,7 +16,6 @@ type AuthSocialProps = {
export function AuthSocial({ label, icon: Icon, strategy }: AuthSocialProps) {
const { startOAuthFlow } = useOAuth({ strategy })
- const { mutateAsync: createUser } = useCreateUserMutation()
const onPress = async () => {
try {
@@ -25,10 +24,14 @@ export function AuthSocial({ label, icon: Icon, strategy }: AuthSocialProps) {
if (createdSessionId) {
setActive?.({ session: createdSessionId })
if (signUp?.createdUserId) {
- setTimeout(async () => await createUser({
- email: signUp.emailAddress!,
- name: signUp.firstName ?? '',
- }), 1000)
+ setTimeout(
+ async () =>
+ await createUser({
+ email: signUp.emailAddress!,
+ name: signUp.firstName ?? '',
+ }),
+ 1000,
+ )
}
} else {
// Use signIn or signUp for next steps such as MFA
diff --git a/apps/mobile/components/common/add-new-button.tsx b/apps/mobile/components/common/add-new-button.tsx
new file mode 100644
index 00000000..d3ff8593
--- /dev/null
+++ b/apps/mobile/components/common/add-new-button.tsx
@@ -0,0 +1,26 @@
+import { cn } from "@/lib/utils"
+import { PlusCircleIcon } from "lucide-react-native"
+import { Button } from "../ui/button"
+import { Text } from "../ui/text"
+
+type AddNewButtonProps = {
+ label: string
+ className?: string
+ onPress?: () => void
+}
+
+export function AddNewButton(
+ { label, className, onPress }: AddNewButtonProps,
+) {
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/apps/mobile/components/common/back-button.tsx b/apps/mobile/components/common/back-button.tsx
new file mode 100644
index 00000000..9d72bb18
--- /dev/null
+++ b/apps/mobile/components/common/back-button.tsx
@@ -0,0 +1,15 @@
+import { useRouter } from "expo-router";
+import { ArrowLeftIcon } from "lucide-react-native";
+import { Button } from "../ui/button";
+
+export function BackButton() {
+ const router = useRouter()
+ if (!router.canGoBack) {
+ return null
+ }
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/apps/mobile/components/common/currency-sheet.tsx b/apps/mobile/components/common/currency-sheet.tsx
new file mode 100644
index 00000000..6b216866
--- /dev/null
+++ b/apps/mobile/components/common/currency-sheet.tsx
@@ -0,0 +1,59 @@
+import { currencies } from "@6pm/currency";
+import { BottomSheetFlatList, BottomSheetTextInput } from "@gorhom/bottom-sheet";
+import { t } from "@lingui/macro";
+import { useLingui } from "@lingui/react";
+import { SearchIcon } from "lucide-react-native";
+import { useState } from "react";
+import { View } from "react-native";
+import { Text } from "../ui/text";
+import { MenuItem } from "./menu-item";
+
+type CurrencySheetListProps = {
+ onSelect: (currency: typeof currencies[number]) => void;
+ value: string;
+}
+
+export function CurrencySheetList({ onSelect, value }: CurrencySheetListProps) {
+ const { i18n } = useLingui()
+ const [searchValue, setSearchValue] = useState('')
+
+ const filteredCurrencies = currencies.filter((currency) => {
+ const search = searchValue.toLowerCase()
+ return currency.name.toLowerCase().includes(search) || currency.code.toLowerCase().includes(search)
+ })
+
+ return (
+ i.code}
+ contentContainerClassName="pb-8"
+ stickyHeaderIndices={[0]}
+ keyboardShouldPersistTaps="handled"
+ keyboardDismissMode="on-drag"
+ ListHeaderComponent={
+
+
+
+
+ }
+ renderItem={({ item }) => (
+ onSelect(item)}
+ className={item.code === value ? 'bg-muted' : ''}
+ rightSection={
+
+ {item.code}
+
+ }
+ />
+ )}
+ />
+ )
+}
diff --git a/apps/mobile/components/common/generic-icon.tsx b/apps/mobile/components/common/generic-icon.tsx
new file mode 100644
index 00000000..6c017623
--- /dev/null
+++ b/apps/mobile/components/common/generic-icon.tsx
@@ -0,0 +1,18 @@
+import { type LucideProps, icons } from 'lucide-react-native'
+import type { FC } from 'react'
+
+/**
+* TODO: Only export the icons that are used to reduce the bundle size
+*/
+
+const GenericIcon: FC<
+ LucideProps & {
+ name: keyof typeof icons
+ }
+> = ({ name, ...props }) => {
+ const LucideIcon = icons[name]
+
+ return
+}
+
+export default GenericIcon
diff --git a/apps/mobile/components/common/icon-grid-sheet.tsx b/apps/mobile/components/common/icon-grid-sheet.tsx
new file mode 100644
index 00000000..5a3779f6
--- /dev/null
+++ b/apps/mobile/components/common/icon-grid-sheet.tsx
@@ -0,0 +1,36 @@
+import { BottomSheetFlatList } from '@gorhom/bottom-sheet'
+import { Button } from '../ui/button'
+import GenericIcon from './generic-icon'
+
+type IconGridSheetProps = {
+ icons: string[]
+ onSelect: (icon: string) => void
+ value: string
+}
+
+export function IconGridSheet({ icons, onSelect, value }: IconGridSheetProps) {
+ return (
+ i}
+ contentContainerClassName="pb-8 px-4 gap-2"
+ columnWrapperClassName="gap-2"
+ showsVerticalScrollIndicator={false}
+ showsHorizontalScrollIndicator={false}
+ keyboardShouldPersistTaps="handled"
+ keyboardDismissMode="on-drag"
+ renderItem={({ item }) => (
+
+ )}
+ />
+ )
+}
diff --git a/apps/mobile/components/common/menu-item.tsx b/apps/mobile/components/common/menu-item.tsx
index bb318cec..ddebda30 100644
--- a/apps/mobile/components/common/menu-item.tsx
+++ b/apps/mobile/components/common/menu-item.tsx
@@ -23,7 +23,7 @@ export const MenuItem = forwardRef(function(
ref={ref}
disabled={disabled}
className={cn(
- "flex flex-row items-center gap-4 px-6 justify-between h-12 active:bg-muted",
+ "flex flex-row items-center gap-4 px-6 justify-between h-14 active:bg-muted",
disabled && "opacity-50",
className,
)}
diff --git a/apps/mobile/components/form-fields/input-field.tsx b/apps/mobile/components/form-fields/input-field.tsx
index aa761d24..30b62a37 100644
--- a/apps/mobile/components/form-fields/input-field.tsx
+++ b/apps/mobile/components/form-fields/input-field.tsx
@@ -1,8 +1,9 @@
import { cn } from '@/lib/utils'
import { useController } from 'react-hook-form'
-import { Text, type TextInputProps, View } from 'react-native'
+import { Text, type TextInputProps, View, type TextInput } from 'react-native'
import { Input } from '../ui/input'
import { Label } from '../ui/label'
+import { forwardRef } from 'react'
type InputFieldProps = TextInputProps & {
name: string
@@ -11,47 +12,53 @@ type InputFieldProps = TextInputProps & {
rightSection?: React.ReactNode
}
-export const InputField: React.FC = ({
- name,
- label,
- leftSection,
- rightSection,
- className,
- ...props
-}) => {
- const {
- field: { onChange, onBlur, value },
- fieldState,
- } = useController({ name })
- return (
-
- {!!label && }
-
- {leftSection && (
-
- {leftSection}
-
- )}
- ,
+ ) => {
+ const {
+ field: { onChange, onBlur, value },
+ fieldState,
+ } = useController({ name })
+ return (
+
+ {!!label && }
+
+ {leftSection && (
+
+ {leftSection}
+
+ )}
+
+ {rightSection && (
+
+ {rightSection}
+
)}
- {...props}
- />
- {rightSection && (
-
- {rightSection}
-
+
+ {!!fieldState.error && (
+ {fieldState.error.message}
)}
- {!!fieldState.error && (
- {fieldState.error.message}
- )}
-
- )
-}
+ )
+ },
+)
diff --git a/apps/mobile/components/navigation/TabBarIcon.tsx b/apps/mobile/components/navigation/TabBarIcon.tsx
deleted file mode 100644
index f06993bf..00000000
--- a/apps/mobile/components/navigation/TabBarIcon.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-// You can explore the built-in icon families and icons on the web at https://icons.expo.fyi/
-
-import Ionicons from '@expo/vector-icons/Ionicons'
-import type { IconProps } from '@expo/vector-icons/build/createIconSet'
-import type { ComponentProps } from 'react'
-
-export function TabBarIcon({
- style,
- ...rest
-}: IconProps['name']>) {
- return
-}
diff --git a/apps/mobile/components/ui/input.tsx b/apps/mobile/components/ui/input.tsx
index f44ede87..f525c444 100644
--- a/apps/mobile/components/ui/input.tsx
+++ b/apps/mobile/components/ui/input.tsx
@@ -10,11 +10,11 @@ const Input = React.forwardRef<
);
diff --git a/apps/mobile/components/wallet/account-form.tsx b/apps/mobile/components/wallet/account-form.tsx
new file mode 100644
index 00000000..bf21de26
--- /dev/null
+++ b/apps/mobile/components/wallet/account-form.tsx
@@ -0,0 +1,76 @@
+import { type AccountFormValues, zAccountFormValues } from '@6pm/validation'
+import { zodResolver } from '@hookform/resolvers/zod'
+import { t } from '@lingui/macro'
+import { useLingui } from '@lingui/react'
+import { useRef } from 'react'
+import { FormProvider, useForm } from 'react-hook-form'
+import { View } from 'react-native'
+import type { TextInput } from 'react-native'
+import { InputField } from '../form-fields/input-field'
+import { SubmitButton } from '../form-fields/submit-button'
+import { Text } from '../ui/text'
+import { SelectAccountIconField } from './select-account-icon-field'
+import { SelectCurrencyField } from './select-currency-field'
+
+type AccountFormProps = {
+ onSubmit: (data: AccountFormValues) => void
+}
+
+export const AccountForm = ({ onSubmit }: AccountFormProps) => {
+ const { i18n } = useLingui()
+ const nameInputRef = useRef(null)
+ const balanceInputRef = useRef(null)
+
+ const accountForm = useForm({
+ resolver: zodResolver(zAccountFormValues),
+ defaultValues: {
+ name: '',
+ preferredCurrency: 'USD', // TODO: get from user settings
+ icon: 'CreditCard',
+ },
+ })
+
+ return (
+
+
+ nameInputRef.current?.focus()}
+ />
+ }
+ onSubmitEditing={() => {
+ balanceInputRef.current?.focus()
+ }}
+ />
+ balanceInputRef.current?.focus()}
+ />
+ }
+ />
+
+ {t(i18n)`Save`}
+
+
+
+ )
+}
diff --git a/apps/mobile/components/wallet/select-account-icon-field.tsx b/apps/mobile/components/wallet/select-account-icon-field.tsx
new file mode 100644
index 00000000..2a78874a
--- /dev/null
+++ b/apps/mobile/components/wallet/select-account-icon-field.tsx
@@ -0,0 +1,62 @@
+import { BottomSheetBackdrop, BottomSheetModal } from '@gorhom/bottom-sheet'
+import { useRef } from 'react'
+
+import { WALLET_ICONS } from '@/lib/icons/wallet-icons'
+import { useController } from 'react-hook-form'
+import { Keyboard } from 'react-native'
+import GenericIcon from '../common/generic-icon'
+import { IconGridSheet } from '../common/icon-grid-sheet'
+import { Button } from '../ui/button'
+
+export function SelectAccountIconField({
+ onSelect,
+}: {
+ onSelect?: (currency: string) => void
+}) {
+ const sheetRef = useRef(null)
+ const {
+ field: { onChange, onBlur, value },
+ // fieldState,
+ } = useController({ name: 'icon' })
+
+ return (
+ <>
+
+ (
+
+ )}
+ >
+ {
+ onChange(icon)
+ sheetRef.current?.close()
+ onBlur()
+ onSelect?.(icon)
+ }}
+ />
+
+ >
+ )
+}
diff --git a/apps/mobile/components/wallet/select-currency-field.tsx b/apps/mobile/components/wallet/select-currency-field.tsx
new file mode 100644
index 00000000..1409594d
--- /dev/null
+++ b/apps/mobile/components/wallet/select-currency-field.tsx
@@ -0,0 +1,60 @@
+import { BottomSheetBackdrop, BottomSheetModal } from '@gorhom/bottom-sheet'
+import { useRef } from 'react'
+
+import { useController } from 'react-hook-form'
+import { Keyboard } from 'react-native'
+import { CurrencySheetList } from '../common/currency-sheet'
+import { Button } from '../ui/button'
+import { Text } from '../ui/text'
+
+export function SelectCurrencyField({
+ onSelect,
+}: {
+ onSelect?: (currency: string) => void
+}) {
+ const sheetRef = useRef(null)
+ const {
+ field: { onChange, onBlur, value },
+ // fieldState,
+ } = useController({ name: 'preferredCurrency' })
+
+ return (
+ <>
+
+ (
+
+ )}
+ >
+ {
+ onChange(currency.code)
+ sheetRef.current?.close()
+ onBlur()
+ onSelect?.(currency.code)
+ }}
+ />
+
+ >
+ )
+}
diff --git a/apps/mobile/components/wallet/wallet-account-item.tsx b/apps/mobile/components/wallet/wallet-account-item.tsx
new file mode 100644
index 00000000..b4e3cd77
--- /dev/null
+++ b/apps/mobile/components/wallet/wallet-account-item.tsx
@@ -0,0 +1,35 @@
+import type { UserWalletAccount } from '@6pm/api'
+import { ChevronRightIcon } from 'lucide-react-native'
+import type { FC } from 'react'
+import { View } from 'react-native'
+import GenericIcon from '../common/generic-icon'
+import { MenuItem } from '../common/menu-item'
+import { Text } from '../ui/text'
+
+type WalletAccountItemProps = {
+ data: UserWalletAccount & { balance: number }
+}
+
+export const WalletAccountItem: FC = ({ data }) => {
+ return (
+ (
+
+ name={data.icon as any}
+ className="size-6 text-foreground"
+ />
+ )}
+ rightSection={
+
+
+ {data.balance.toLocaleString()}{' '}
+ {data.preferredCurrency}
+
+
+
+ }
+ />
+ )
+}
diff --git a/apps/mobile/lib/icons/wallet-icons.ts b/apps/mobile/lib/icons/wallet-icons.ts
new file mode 100644
index 00000000..870310d6
--- /dev/null
+++ b/apps/mobile/lib/icons/wallet-icons.ts
@@ -0,0 +1,25 @@
+import type { icons } from 'lucide-react-native'
+
+export const WALLET_ICONS: Array = [
+ 'WalletMinimal',
+ 'Coins',
+ 'Banknote',
+ 'Bitcoin',
+ 'CreditCard',
+ 'Gem',
+ 'HandCoins',
+ 'Handshake',
+ 'PiggyBank',
+ 'SmartphoneNfc',
+ 'BadgeCent',
+ 'BadgeDollarSign',
+ 'BadgeEuro',
+ 'BadgeIndianRupee',
+ 'BadgeJapaneseYen',
+ 'BadgePoundSterling',
+ 'BadgeRussianRuble',
+ 'BadgeSwissFranc',
+ // 'Paintbrush',
+ // 'BrickWall',
+ // 'CookingPot',
+]
diff --git a/apps/mobile/mutations/user.ts b/apps/mobile/mutations/user.ts
index 65ef9d31..14b8920d 100644
--- a/apps/mobile/mutations/user.ts
+++ b/apps/mobile/mutations/user.ts
@@ -1,14 +1,9 @@
import { getHonoClient } from '@/lib/client'
-import type { CreateUser } from '@6pm/api'
-import { useMutation } from '@tanstack/react-query'
+import type { CreateUser } from '@6pm/validation'
-export function useCreateUserMutation() {
- return useMutation({
- mutationFn: async (data: CreateUser) => {
- const hc = await getHonoClient()
- await hc.v1.users.$post({
- json: data,
- })
- },
+export async function createUser(data: CreateUser) {
+ const hc = await getHonoClient()
+ await hc.v1.users.$post({
+ json: data,
})
}
diff --git a/apps/mobile/mutations/wallet.ts b/apps/mobile/mutations/wallet.ts
new file mode 100644
index 00000000..3fbd9728
--- /dev/null
+++ b/apps/mobile/mutations/wallet.ts
@@ -0,0 +1,38 @@
+import { getHonoClient } from '@/lib/client'
+import type { AccountFormValues, UpdateWallet } from '@6pm/validation'
+
+export async function createWallet(data: AccountFormValues) {
+ const { balance, ...walletData } = data
+ const hc = await getHonoClient()
+ const result = await hc.v1.wallets.wallets.$post({
+ json: walletData,
+ })
+ if (result.ok) {
+ const wallet = await result.json()
+ await hc.v1.transactions.$post({
+ json: {
+ amount: balance ?? 0,
+ walletAccountId: wallet.id,
+ currency: wallet.preferredCurrency,
+ date: new Date(),
+ note: 'Initial balance',
+ },
+ })
+ }
+ return result
+}
+
+export async function updateWallet(id: string, data: UpdateWallet) {
+ const hc = await getHonoClient()
+ await hc.v1.wallets.wallets[':walletId'].$put({
+ param: { walletId: id },
+ json: data,
+ })
+}
+
+export async function deleteWallet(id: string) {
+ const hc = await getHonoClient()
+ await hc.v1.wallets.wallets[':walletId'].$delete({
+ param: { walletId: id },
+ })
+}
diff --git a/apps/mobile/package.json b/apps/mobile/package.json
index 7ba6d95f..521b6a88 100644
--- a/apps/mobile/package.json
+++ b/apps/mobile/package.json
@@ -17,15 +17,18 @@
},
"dependencies": {
"@6pm/api": "workspace:^",
+ "@6pm/currency": "workspace:^",
"@6pm/validation": "workspace:^",
"@clerk/clerk-expo": "^1.2.0",
"@expo-google-fonts/be-vietnam-pro": "^0.2.3",
"@expo/vector-icons": "^14.0.0",
"@formatjs/intl-locale": "^4.0.0",
"@formatjs/intl-pluralrules": "^5.2.14",
+ "@gorhom/bottom-sheet": "^4.6.3",
"@hookform/resolvers": "^3.6.0",
"@lingui/macro": "^4.11.1",
"@lingui/react": "^4.11.1",
+ "@lukemorales/query-key-factory": "^1.3.4",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4",
diff --git a/apps/mobile/queries/wallet.ts b/apps/mobile/queries/wallet.ts
new file mode 100644
index 00000000..511d7ac0
--- /dev/null
+++ b/apps/mobile/queries/wallet.ts
@@ -0,0 +1,21 @@
+import { getHonoClient } from '@/lib/client'
+import { createQueryKeys } from '@lukemorales/query-key-factory'
+import { useQuery } from '@tanstack/react-query'
+
+export const walletQueries = createQueryKeys('wallet', {
+ list: () => ({
+ queryKey: [{}],
+ queryFn: async () => {
+ const hc = await getHonoClient()
+ const res = await hc.v1.wallets.wallets.$get()
+ if (!res.ok) {
+ throw new Error(await res.text())
+ }
+ return await res.json()
+ },
+ }),
+})
+
+export function useWallets() {
+ return useQuery(walletQueries.list())
+}
diff --git a/packages/currency/README.md b/packages/currency/README.md
new file mode 100644
index 00000000..24214aea
--- /dev/null
+++ b/packages/currency/README.md
@@ -0,0 +1 @@
+# @6pm/currency
diff --git a/packages/currency/package.json b/packages/currency/package.json
new file mode 100644
index 00000000..83817427
--- /dev/null
+++ b/packages/currency/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "@6pm/currency",
+ "version": "1.0.0",
+ "description": "",
+ "main": "src/index.ts",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC"
+}
diff --git a/packages/currency/src/currencies.json b/packages/currency/src/currencies.json
new file mode 100644
index 00000000..87d0340b
--- /dev/null
+++ b/packages/currency/src/currencies.json
@@ -0,0 +1,1073 @@
+[
+ {
+ "name": "US Dollar",
+ "symbol": "$",
+ "symbolNative": "$",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "USD",
+ "namePlural": "US dollars"
+ },
+ {
+ "name": "Canadian Dollar",
+ "symbol": "CA$",
+ "symbolNative": "$",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "CAD",
+ "namePlural": "Canadian dollars"
+ },
+ {
+ "name": "Euro",
+ "symbol": "€",
+ "symbolNative": "€",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "EUR",
+ "namePlural": "euros"
+ },
+ {
+ "name": "United Arab Emirates Dirham",
+ "symbol": "AED",
+ "symbolNative": "د.إ.",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "AED",
+ "namePlural": "UAE dirhams"
+ },
+ {
+ "name": "Afghan Afghani",
+ "symbol": "Af",
+ "symbolNative": "؋",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "AFN",
+ "namePlural": "Afghan Afghanis"
+ },
+ {
+ "name": "Albanian Lek",
+ "symbol": "ALL",
+ "symbolNative": "Lek",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "ALL",
+ "namePlural": "Albanian lekë"
+ },
+ {
+ "name": "Armenian Dram",
+ "symbol": "AMD",
+ "symbolNative": "դր.",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "AMD",
+ "namePlural": "Armenian drams"
+ },
+ {
+ "name": "Argentine Peso",
+ "symbol": "AR$",
+ "symbolNative": "$",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "ARS",
+ "namePlural": "Argentine pesos"
+ },
+ {
+ "name": "Australian Dollar",
+ "symbol": "AU$",
+ "symbolNative": "$",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "AUD",
+ "namePlural": "Australian dollars"
+ },
+ {
+ "name": "Azerbaijani Manat",
+ "symbol": "man.",
+ "symbolNative": "ман.",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "AZN",
+ "namePlural": "Azerbaijani manats"
+ },
+ {
+ "name": "Bosnia-Herzegovina Convertible Mark",
+ "symbol": "KM",
+ "symbolNative": "KM",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "BAM",
+ "namePlural": "Bosnia-Herzegovina convertible marks"
+ },
+ {
+ "name": "Bangladeshi Taka",
+ "symbol": "Tk",
+ "symbolNative": "৳",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "BDT",
+ "namePlural": "Bangladeshi takas"
+ },
+ {
+ "name": "Bulgarian Lev",
+ "symbol": "BGN",
+ "symbolNative": "лв.",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "BGN",
+ "namePlural": "Bulgarian leva"
+ },
+ {
+ "name": "Bahraini Dinar",
+ "symbol": "BD",
+ "symbolNative": "د.ب.",
+ "decimalDigits": 3,
+ "rounding": 0,
+ "code": "BHD",
+ "namePlural": "Bahraini dinars"
+ },
+ {
+ "name": "Burundian Franc",
+ "symbol": "FBu",
+ "symbolNative": "FBu",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "BIF",
+ "namePlural": "Burundian francs"
+ },
+ {
+ "name": "Brunei Dollar",
+ "symbol": "BN$",
+ "symbolNative": "$",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "BND",
+ "namePlural": "Brunei dollars"
+ },
+ {
+ "name": "Bolivian Boliviano",
+ "symbol": "Bs",
+ "symbolNative": "Bs",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "BOB",
+ "namePlural": "Bolivian bolivianos"
+ },
+ {
+ "name": "Brazilian Real",
+ "symbol": "R$",
+ "symbolNative": "R$",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "BRL",
+ "namePlural": "Brazilian reals"
+ },
+ {
+ "name": "Botswanan Pula",
+ "symbol": "BWP",
+ "symbolNative": "P",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "BWP",
+ "namePlural": "Botswanan pulas"
+ },
+ {
+ "name": "Belarusian Ruble",
+ "symbol": "Br",
+ "symbolNative": "руб.",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "BYN",
+ "namePlural": "Belarusian rubles"
+ },
+ {
+ "name": "Belize Dollar",
+ "symbol": "BZ$",
+ "symbolNative": "$",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "BZD",
+ "namePlural": "Belize dollars"
+ },
+ {
+ "name": "Congolese Franc",
+ "symbol": "CDF",
+ "symbolNative": "FrCD",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "CDF",
+ "namePlural": "Congolese francs"
+ },
+ {
+ "name": "Swiss Franc",
+ "symbol": "CHF",
+ "symbolNative": "CHF",
+ "decimalDigits": 2,
+ "rounding": 0.05,
+ "code": "CHF",
+ "namePlural": "Swiss francs"
+ },
+ {
+ "name": "Chilean Peso",
+ "symbol": "CL$",
+ "symbolNative": "$",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "CLP",
+ "namePlural": "Chilean pesos"
+ },
+ {
+ "name": "Chinese Yuan",
+ "symbol": "CN¥",
+ "symbolNative": "CN¥",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "CNY",
+ "namePlural": "Chinese yuan"
+ },
+ {
+ "name": "Colombian Peso",
+ "symbol": "CO$",
+ "symbolNative": "$",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "COP",
+ "namePlural": "Colombian pesos"
+ },
+ {
+ "name": "Costa Rican Colón",
+ "symbol": "₡",
+ "symbolNative": "₡",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "CRC",
+ "namePlural": "Costa Rican colóns"
+ },
+ {
+ "name": "Cape Verdean Escudo",
+ "symbol": "CV$",
+ "symbolNative": "CV$",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "CVE",
+ "namePlural": "Cape Verdean escudos"
+ },
+ {
+ "name": "Czech Republic Koruna",
+ "symbol": "Kč",
+ "symbolNative": "Kč",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "CZK",
+ "namePlural": "Czech Republic korunas"
+ },
+ {
+ "name": "Djiboutian Franc",
+ "symbol": "Fdj",
+ "symbolNative": "Fdj",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "DJF",
+ "namePlural": "Djiboutian francs"
+ },
+ {
+ "name": "Danish Krone",
+ "symbol": "Dkr",
+ "symbolNative": "kr",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "DKK",
+ "namePlural": "Danish kroner"
+ },
+ {
+ "name": "Dominican Peso",
+ "symbol": "RD$",
+ "symbolNative": "RD$",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "DOP",
+ "namePlural": "Dominican pesos"
+ },
+ {
+ "name": "Algerian Dinar",
+ "symbol": "DA",
+ "symbolNative": "د.ج.",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "DZD",
+ "namePlural": "Algerian dinars"
+ },
+ {
+ "name": "Estonian Kroon",
+ "symbol": "Ekr",
+ "symbolNative": "kr",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "EEK",
+ "namePlural": "Estonian kroons"
+ },
+ {
+ "name": "Egyptian Pound",
+ "symbol": "EGP",
+ "symbolNative": "ج.م.",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "EGP",
+ "namePlural": "Egyptian pounds"
+ },
+ {
+ "name": "Eritrean Nakfa",
+ "symbol": "Nfk",
+ "symbolNative": "Nfk",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "ERN",
+ "namePlural": "Eritrean nakfas"
+ },
+ {
+ "name": "Ethiopian Birr",
+ "symbol": "Br",
+ "symbolNative": "Br",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "ETB",
+ "namePlural": "Ethiopian birrs"
+ },
+ {
+ "name": "British Pound Sterling",
+ "symbol": "£",
+ "symbolNative": "£",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "GBP",
+ "namePlural": "British pounds sterling"
+ },
+ {
+ "name": "Georgian Lari",
+ "symbol": "GEL",
+ "symbolNative": "GEL",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "GEL",
+ "namePlural": "Georgian laris"
+ },
+ {
+ "name": "Ghanaian Cedi",
+ "symbol": "GH₵",
+ "symbolNative": "GH₵",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "GHS",
+ "namePlural": "Ghanaian cedis"
+ },
+ {
+ "name": "Guinean Franc",
+ "symbol": "FG",
+ "symbolNative": "FG",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "GNF",
+ "namePlural": "Guinean francs"
+ },
+ {
+ "name": "Guatemalan Quetzal",
+ "symbol": "GTQ",
+ "symbolNative": "Q",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "GTQ",
+ "namePlural": "Guatemalan quetzals"
+ },
+ {
+ "name": "Hong Kong Dollar",
+ "symbol": "HK$",
+ "symbolNative": "$",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "HKD",
+ "namePlural": "Hong Kong dollars"
+ },
+ {
+ "name": "Honduran Lempira",
+ "symbol": "HNL",
+ "symbolNative": "L",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "HNL",
+ "namePlural": "Honduran lempiras"
+ },
+ {
+ "name": "Croatian Kuna",
+ "symbol": "kn",
+ "symbolNative": "kn",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "HRK",
+ "namePlural": "Croatian kunas"
+ },
+ {
+ "name": "Hungarian Forint",
+ "symbol": "Ft",
+ "symbolNative": "Ft",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "HUF",
+ "namePlural": "Hungarian forints"
+ },
+ {
+ "name": "Indonesian Rupiah",
+ "symbol": "Rp",
+ "symbolNative": "Rp",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "IDR",
+ "namePlural": "Indonesian rupiahs"
+ },
+ {
+ "name": "Israeli New Sheqel",
+ "symbol": "₪",
+ "symbolNative": "₪",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "ILS",
+ "namePlural": "Israeli new sheqels"
+ },
+ {
+ "name": "Indian Rupee",
+ "symbol": "Rs",
+ "symbolNative": "টকা",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "INR",
+ "namePlural": "Indian rupees"
+ },
+ {
+ "name": "Iraqi Dinar",
+ "symbol": "IQD",
+ "symbolNative": "د.ع.",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "IQD",
+ "namePlural": "Iraqi dinars"
+ },
+ {
+ "name": "Iranian Rial",
+ "symbol": "IRR",
+ "symbolNative": "﷼",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "IRR",
+ "namePlural": "Iranian rials"
+ },
+ {
+ "name": "Icelandic Króna",
+ "symbol": "Ikr",
+ "symbolNative": "kr",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "ISK",
+ "namePlural": "Icelandic krónur"
+ },
+ {
+ "name": "Jamaican Dollar",
+ "symbol": "J$",
+ "symbolNative": "$",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "JMD",
+ "namePlural": "Jamaican dollars"
+ },
+ {
+ "name": "Jordanian Dinar",
+ "symbol": "JD",
+ "symbolNative": "د.أ.",
+ "decimalDigits": 3,
+ "rounding": 0,
+ "code": "JOD",
+ "namePlural": "Jordanian dinars"
+ },
+ {
+ "name": "Japanese Yen",
+ "symbol": "¥",
+ "symbolNative": "¥",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "JPY",
+ "namePlural": "Japanese yen"
+ },
+ {
+ "name": "Kenyan Shilling",
+ "symbol": "Ksh",
+ "symbolNative": "Ksh",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "KES",
+ "namePlural": "Kenyan shillings"
+ },
+ {
+ "name": "Cambodian Riel",
+ "symbol": "KHR",
+ "symbolNative": "៛",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "KHR",
+ "namePlural": "Cambodian riels"
+ },
+ {
+ "name": "Comorian Franc",
+ "symbol": "CF",
+ "symbolNative": "FC",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "KMF",
+ "namePlural": "Comorian francs"
+ },
+ {
+ "name": "South Korean Won",
+ "symbol": "₩",
+ "symbolNative": "₩",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "KRW",
+ "namePlural": "South Korean won"
+ },
+ {
+ "name": "Kuwaiti Dinar",
+ "symbol": "KD",
+ "symbolNative": "د.ك.",
+ "decimalDigits": 3,
+ "rounding": 0,
+ "code": "KWD",
+ "namePlural": "Kuwaiti dinars"
+ },
+ {
+ "name": "Kazakhstani Tenge",
+ "symbol": "KZT",
+ "symbolNative": "тңг.",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "KZT",
+ "namePlural": "Kazakhstani tenges"
+ },
+ {
+ "name": "Lebanese Pound",
+ "symbol": "LB£",
+ "symbolNative": "ل.ل.",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "LBP",
+ "namePlural": "Lebanese pounds"
+ },
+ {
+ "name": "Sri Lankan Rupee",
+ "symbol": "SLRs",
+ "symbolNative": "SL Re",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "LKR",
+ "namePlural": "Sri Lankan rupees"
+ },
+ {
+ "name": "Lithuanian Litas",
+ "symbol": "Lt",
+ "symbolNative": "Lt",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "LTL",
+ "namePlural": "Lithuanian litai"
+ },
+ {
+ "name": "Latvian Lats",
+ "symbol": "Ls",
+ "symbolNative": "Ls",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "LVL",
+ "namePlural": "Latvian lati"
+ },
+ {
+ "name": "Libyan Dinar",
+ "symbol": "LD",
+ "symbolNative": "د.ل.",
+ "decimalDigits": 3,
+ "rounding": 0,
+ "code": "LYD",
+ "namePlural": "Libyan dinars"
+ },
+ {
+ "name": "Moroccan Dirham",
+ "symbol": "MAD",
+ "symbolNative": "د.م.",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "MAD",
+ "namePlural": "Moroccan dirhams"
+ },
+ {
+ "name": "Moldovan Leu",
+ "symbol": "MDL",
+ "symbolNative": "MDL",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "MDL",
+ "namePlural": "Moldovan lei"
+ },
+ {
+ "name": "Malagasy Ariary",
+ "symbol": "MGA",
+ "symbolNative": "MGA",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "MGA",
+ "namePlural": "Malagasy Ariaries"
+ },
+ {
+ "name": "Macedonian Denar",
+ "symbol": "MKD",
+ "symbolNative": "MKD",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "MKD",
+ "namePlural": "Macedonian denari"
+ },
+ {
+ "name": "Myanma Kyat",
+ "symbol": "MMK",
+ "symbolNative": "K",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "MMK",
+ "namePlural": "Myanma kyats"
+ },
+ {
+ "name": "Macanese Pataca",
+ "symbol": "MOP$",
+ "symbolNative": "MOP$",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "MOP",
+ "namePlural": "Macanese patacas"
+ },
+ {
+ "name": "Mauritian Rupee",
+ "symbol": "MURs",
+ "symbolNative": "MURs",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "MUR",
+ "namePlural": "Mauritian rupees"
+ },
+ {
+ "name": "Mexican Peso",
+ "symbol": "MX$",
+ "symbolNative": "$",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "MXN",
+ "namePlural": "Mexican pesos"
+ },
+ {
+ "name": "Malaysian Ringgit",
+ "symbol": "RM",
+ "symbolNative": "RM",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "MYR",
+ "namePlural": "Malaysian ringgits"
+ },
+ {
+ "name": "Mozambican Metical",
+ "symbol": "MTn",
+ "symbolNative": "MTn",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "MZN",
+ "namePlural": "Mozambican meticals"
+ },
+ {
+ "name": "Namibian Dollar",
+ "symbol": "N$",
+ "symbolNative": "N$",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "NAD",
+ "namePlural": "Namibian dollars"
+ },
+ {
+ "name": "Nigerian Naira",
+ "symbol": "₦",
+ "symbolNative": "₦",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "NGN",
+ "namePlural": "Nigerian nairas"
+ },
+ {
+ "name": "Nicaraguan Córdoba",
+ "symbol": "C$",
+ "symbolNative": "C$",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "NIO",
+ "namePlural": "Nicaraguan córdobas"
+ },
+ {
+ "name": "Norwegian Krone",
+ "symbol": "Nkr",
+ "symbolNative": "kr",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "NOK",
+ "namePlural": "Norwegian kroner"
+ },
+ {
+ "name": "Nepalese Rupee",
+ "symbol": "NPRs",
+ "symbolNative": "नेरू",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "NPR",
+ "namePlural": "Nepalese rupees"
+ },
+ {
+ "name": "New Zealand Dollar",
+ "symbol": "NZ$",
+ "symbolNative": "$",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "NZD",
+ "namePlural": "New Zealand dollars"
+ },
+ {
+ "name": "Omani Rial",
+ "symbol": "OMR",
+ "symbolNative": "ر.ع.",
+ "decimalDigits": 3,
+ "rounding": 0,
+ "code": "OMR",
+ "namePlural": "Omani rials"
+ },
+ {
+ "name": "Panamanian Balboa",
+ "symbol": "B/.",
+ "symbolNative": "B/.",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "PAB",
+ "namePlural": "Panamanian balboas"
+ },
+ {
+ "name": "Peruvian Nuevo Sol",
+ "symbol": "S/.",
+ "symbolNative": "S/.",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "PEN",
+ "namePlural": "Peruvian nuevos soles"
+ },
+ {
+ "name": "Philippine Peso",
+ "symbol": "₱",
+ "symbolNative": "₱",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "PHP",
+ "namePlural": "Philippine pesos"
+ },
+ {
+ "name": "Pakistani Rupee",
+ "symbol": "PKRs",
+ "symbolNative": "₨",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "PKR",
+ "namePlural": "Pakistani rupees"
+ },
+ {
+ "name": "Polish Zloty",
+ "symbol": "zł",
+ "symbolNative": "zł",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "PLN",
+ "namePlural": "Polish zlotys"
+ },
+ {
+ "name": "Paraguayan Guarani",
+ "symbol": "₲",
+ "symbolNative": "₲",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "PYG",
+ "namePlural": "Paraguayan guaranis"
+ },
+ {
+ "name": "Qatari Rial",
+ "symbol": "QR",
+ "symbolNative": "ر.ق.",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "QAR",
+ "namePlural": "Qatari rials"
+ },
+ {
+ "name": "Romanian Leu",
+ "symbol": "RON",
+ "symbolNative": "RON",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "RON",
+ "namePlural": "Romanian lei"
+ },
+ {
+ "name": "Serbian Dinar",
+ "symbol": "din.",
+ "symbolNative": "дин.",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "RSD",
+ "namePlural": "Serbian dinars"
+ },
+ {
+ "name": "Russian Ruble",
+ "symbol": "RUB",
+ "symbolNative": "₽.",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "RUB",
+ "namePlural": "Russian rubles"
+ },
+ {
+ "name": "Rwandan Franc",
+ "symbol": "RWF",
+ "symbolNative": "FR",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "RWF",
+ "namePlural": "Rwandan francs"
+ },
+ {
+ "name": "Saudi Riyal",
+ "symbol": "SR",
+ "symbolNative": "ر.س.",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "SAR",
+ "namePlural": "Saudi riyals"
+ },
+ {
+ "name": "Sudanese Pound",
+ "symbol": "SDG",
+ "symbolNative": "SDG",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "SDG",
+ "namePlural": "Sudanese pounds"
+ },
+ {
+ "name": "Swedish Krona",
+ "symbol": "Skr",
+ "symbolNative": "kr",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "SEK",
+ "namePlural": "Swedish kronor"
+ },
+ {
+ "name": "Singapore Dollar",
+ "symbol": "S$",
+ "symbolNative": "$",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "SGD",
+ "namePlural": "Singapore dollars"
+ },
+ {
+ "name": "Somali Shilling",
+ "symbol": "Ssh",
+ "symbolNative": "Ssh",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "SOS",
+ "namePlural": "Somali shillings"
+ },
+ {
+ "name": "Syrian Pound",
+ "symbol": "SY£",
+ "symbolNative": "ل.س.",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "SYP",
+ "namePlural": "Syrian pounds"
+ },
+ {
+ "name": "Thai Baht",
+ "symbol": "฿",
+ "symbolNative": "฿",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "THB",
+ "namePlural": "Thai baht"
+ },
+ {
+ "name": "Tunisian Dinar",
+ "symbol": "DT",
+ "symbolNative": "د.ت.",
+ "decimalDigits": 3,
+ "rounding": 0,
+ "code": "TND",
+ "namePlural": "Tunisian dinars"
+ },
+ {
+ "name": "Tongan Paʻanga",
+ "symbol": "T$",
+ "symbolNative": "T$",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "TOP",
+ "namePlural": "Tongan paʻanga"
+ },
+ {
+ "name": "Turkish Lira",
+ "symbol": "TL",
+ "symbolNative": "TL",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "TRY",
+ "namePlural": "Turkish Lira"
+ },
+ {
+ "name": "Trinidad and Tobago Dollar",
+ "symbol": "TT$",
+ "symbolNative": "$",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "TTD",
+ "namePlural": "Trinidad and Tobago dollars"
+ },
+ {
+ "name": "New Taiwan Dollar",
+ "symbol": "NT$",
+ "symbolNative": "NT$",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "TWD",
+ "namePlural": "New Taiwan dollars"
+ },
+ {
+ "name": "Tanzanian Shilling",
+ "symbol": "TSh",
+ "symbolNative": "TSh",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "TZS",
+ "namePlural": "Tanzanian shillings"
+ },
+ {
+ "name": "Ukrainian Hryvnia",
+ "symbol": "₴",
+ "symbolNative": "₴",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "UAH",
+ "namePlural": "Ukrainian hryvnias"
+ },
+ {
+ "name": "Ugandan Shilling",
+ "symbol": "USh",
+ "symbolNative": "USh",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "UGX",
+ "namePlural": "Ugandan shillings"
+ },
+ {
+ "name": "Uruguayan Peso",
+ "symbol": "$U",
+ "symbolNative": "$",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "UYU",
+ "namePlural": "Uruguayan pesos"
+ },
+ {
+ "name": "Uzbekistan Som",
+ "symbol": "UZS",
+ "symbolNative": "UZS",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "UZS",
+ "namePlural": "Uzbekistan som"
+ },
+ {
+ "name": "Venezuelan Bolívar",
+ "symbol": "Bs.F.",
+ "symbolNative": "Bs.F.",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "VEF",
+ "namePlural": "Venezuelan bolívars"
+ },
+ {
+ "name": "Vietnamese Dong",
+ "symbol": "₫",
+ "symbolNative": "₫",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "VND",
+ "namePlural": "Vietnamese dong"
+ },
+ {
+ "name": "CFA Franc BEAC",
+ "symbol": "FCFA",
+ "symbolNative": "FCFA",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "XAF",
+ "namePlural": "CFA francs BEAC"
+ },
+ {
+ "name": "CFA Franc BCEAO",
+ "symbol": "CFA",
+ "symbolNative": "CFA",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "XOF",
+ "namePlural": "CFA francs BCEAO"
+ },
+ {
+ "name": "Yemeni Rial",
+ "symbol": "YR",
+ "symbolNative": "ر.ي.",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "YER",
+ "namePlural": "Yemeni rials"
+ },
+ {
+ "name": "South African Rand",
+ "symbol": "R",
+ "symbolNative": "R",
+ "decimalDigits": 2,
+ "rounding": 0,
+ "code": "ZAR",
+ "namePlural": "South African rand"
+ },
+ {
+ "name": "Zambian Kwacha",
+ "symbol": "ZK",
+ "symbolNative": "ZK",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "ZMK",
+ "namePlural": "Zambian kwachas"
+ },
+ {
+ "name": "Zimbabwean Dollar",
+ "symbol": "ZWL$",
+ "symbolNative": "ZWL$",
+ "decimalDigits": 0,
+ "rounding": 0,
+ "code": "ZWL",
+ "namePlural": "Zimbabwean Dollar"
+ }
+]
diff --git a/packages/currency/src/index.ts b/packages/currency/src/index.ts
new file mode 100644
index 00000000..180ead69
--- /dev/null
+++ b/packages/currency/src/index.ts
@@ -0,0 +1,3 @@
+import currencies from './currencies.json'
+
+export { currencies }
diff --git a/packages/validation/src/transaction.zod.ts b/packages/validation/src/transaction.zod.ts
index da0cb0a9..526559ec 100644
--- a/packages/validation/src/transaction.zod.ts
+++ b/packages/validation/src/transaction.zod.ts
@@ -1,7 +1,7 @@
import { z } from 'zod'
export const zCreateTransaction = z.object({
- date: z.date(),
+ date: z.date({ coerce: true }),
amount: z.number(),
currency: z.string(),
note: z.string().optional(),
@@ -11,7 +11,7 @@ export const zCreateTransaction = z.object({
export type CreateTransaction = z.infer
export const zUpdateTransaction = z.object({
- date: z.date().optional(),
+ date: z.date({ coerce: true }).optional(),
amount: z.number().optional(),
currency: z.string().optional(),
note: z.string().optional(),
diff --git a/packages/validation/src/wallet.zod.ts b/packages/validation/src/wallet.zod.ts
index dd5d1a66..ccf30433 100644
--- a/packages/validation/src/wallet.zod.ts
+++ b/packages/validation/src/wallet.zod.ts
@@ -17,3 +17,8 @@ export const zUpdateWallet = z.object({
preferredCurrency: z.string().optional(),
})
export type UpdateWallet = z.infer
+
+export const zAccountFormValues = zCreateWallet.extend({
+ balance: z.number({ coerce: true }).positive().optional(),
+})
+export type AccountFormValues = z.infer
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2ff21e9c..90787072 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -93,6 +93,9 @@ importers:
'@6pm/api':
specifier: workspace:^
version: link:../api
+ '@6pm/currency':
+ specifier: workspace:^
+ version: link:../../packages/currency
'@6pm/validation':
specifier: workspace:^
version: link:../../packages/validation
@@ -111,6 +114,9 @@ importers:
'@formatjs/intl-pluralrules':
specifier: ^5.2.14
version: 5.2.14
+ '@gorhom/bottom-sheet':
+ specifier: ^4.6.3
+ version: 4.6.3(@types/react@18.2.79)(react-native-gesture-handler@2.16.2(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1))(react-native-reanimated@3.10.1(@babel/core@7.24.7)(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1))(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1)
'@hookform/resolvers':
specifier: ^3.6.0
version: 3.6.0(react-hook-form@7.52.0(react@18.3.1))
@@ -120,6 +126,9 @@ importers:
'@lingui/react':
specifier: ^4.11.1
version: 4.11.1(react@18.3.1)
+ '@lukemorales/query-key-factory':
+ specifier: ^1.3.4
+ version: 1.3.4(@tanstack/query-core@5.45.0)(@tanstack/react-query@5.45.1(react@18.3.1))
'@radix-ui/react-label':
specifier: ^2.0.2
version: 2.0.2(@types/react-dom@18.3.0)(@types/react@18.2.79)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -1439,6 +1448,27 @@ packages:
'@formkit/auto-animate@0.8.2':
resolution: {integrity: sha512-SwPWfeRa5veb1hOIBMdzI+73te5puUBHmqqaF1Bu7FjvxlYSz/kJcZKSa9Cg60zL0uRNeJL2SbRxV6Jp6Q1nFQ==}
+ '@gorhom/bottom-sheet@4.6.3':
+ resolution: {integrity: sha512-fSuSfbtoKsjmSeyz+tG2C0GtcEL7PS63iEXI23c9M+HeCT1IFK6ffmIa2pqyqB43L1jtkR+BWkpZwqXnN4H8xA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-native': '*'
+ react: '*'
+ react-native: '*'
+ react-native-gesture-handler: '>=1.10.1'
+ react-native-reanimated: '>=2.2.0'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-native':
+ optional: true
+
+ '@gorhom/portal@1.0.14':
+ resolution: {integrity: sha512-MXyL4xvCjmgaORr/rtryDNFy3kU4qUbKlwtQqqsygd0xX3mhKjOLn6mQK8wfu0RkoE0pBE0nAasRoHua+/QZ7A==}
+ peerDependencies:
+ react: '*'
+ react-native: '*'
+
'@graphql-typed-document-node/core@3.2.0':
resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==}
peerDependencies:
@@ -1621,6 +1651,13 @@ packages:
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ '@lukemorales/query-key-factory@1.3.4':
+ resolution: {integrity: sha512-A3frRDdkmaNNQi6mxIshsDk4chRXWoXa05US8fBo4kci/H+lVmujS6QrwQLLGIkNIRFGjMqp2uKjC4XsLdydRw==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@tanstack/query-core': '>= 4.0.0'
+ '@tanstack/react-query': '>= 4.0.0'
+
'@messageformat/parser@5.1.0':
resolution: {integrity: sha512-jKlkls3Gewgw6qMjKZ9SFfHUpdzEVdovKFtW1qRhJ3WI4FW5R/NnGDqr8SDGz+krWDO3ki94boMmQvGke1HwUQ==}
@@ -7927,6 +7964,23 @@ snapshots:
'@formkit/auto-animate@0.8.2': {}
+ '@gorhom/bottom-sheet@4.6.3(@types/react@18.2.79)(react-native-gesture-handler@2.16.2(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1))(react-native-reanimated@3.10.1(@babel/core@7.24.7)(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1))(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@gorhom/portal': 1.0.14(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1)
+ invariant: 2.2.4
+ react: 18.3.1
+ react-native: 0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.3.1)
+ react-native-gesture-handler: 2.16.2(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1)
+ react-native-reanimated: 3.10.1(@babel/core@7.24.7)(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.2.79
+
+ '@gorhom/portal@1.0.14(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ nanoid: 3.3.7
+ react: 18.3.1
+ react-native: 0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.3.1)
+
'@graphql-typed-document-node/core@3.2.0(graphql@15.8.0)':
dependencies:
graphql: 15.8.0
@@ -8279,6 +8333,11 @@ snapshots:
'@lingui/core': 4.11.1
react: 18.3.1
+ '@lukemorales/query-key-factory@1.3.4(@tanstack/query-core@5.45.0)(@tanstack/react-query@5.45.1(react@18.3.1))':
+ dependencies:
+ '@tanstack/query-core': 5.45.0
+ '@tanstack/react-query': 5.45.1(react@18.3.1)
+
'@messageformat/parser@5.1.0':
dependencies:
moo: 0.5.2