Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(mobile): [Wallet] add basic account form with icon and currency input #67

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/mobile/app/(app)/(tabs)/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export default function SettingsScreen() {
{t(i18n)`General`}
</Text>
<View>
<Link href="/wallet-accounts" asChild disabled>
<Link href="/wallet/accounts" asChild>
<MenuItem
label={t(i18n)`Wallet accounts`}
icon={WalletCardsIcon}
Expand Down
61 changes: 46 additions & 15 deletions apps/mobile/app/(app)/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { BackButton } from '@/components/common/back-button'
import { Button } from '@/components/ui/button'
import { useColorScheme } from '@/hooks/useColorScheme'
import { theme } from '@/lib/theme'
import { useAuth } from '@clerk/clerk-expo'
import { t } from '@lingui/macro'
import { useLingui } from '@lingui/react'
import { Redirect, SplashScreen, Stack } from 'expo-router'
import { Link, Redirect, SplashScreen, Stack } from 'expo-router'
import { PlusIcon } from 'lucide-react-native'
import { useEffect } from 'react'

export default function AuthenticatedLayout() {
Expand All @@ -22,35 +25,63 @@ export default function AuthenticatedLayout() {
}

return (
<Stack screenOptions={{ headerShown: false }}>
<Stack screenOptions={{
headerShown: true,
headerBackTitleVisible: false,
headerTintColor: theme[colorScheme ?? 'light'].primary,
headerShadowVisible: false,
headerTitleStyle: {
fontFamily: 'Be Vietnam Pro Medium',
fontSize: 16,
color: theme[colorScheme ?? 'light'].primary,
},
headerStyle: {
backgroundColor: theme[colorScheme ?? 'light'].background,
},
headerLeft: () => <BackButton />,
}}>
<Stack.Screen
name="(tabs)"
options={{
headerShown: false,
}}
/>
<Stack.Screen
name="new-record"
options={{
presentation: 'modal',
headerShown: false
}}
/>
<Stack.Screen
name="language"
options={{
presentation: 'modal',
headerTitle: t(i18n)`Language`,
}}
/>
<Stack.Screen
name="appearance"
options={{ headerTitle: t(i18n)`Appearance` }}
/>
<Stack.Screen
name="wallet/accounts"
options={{
headerTitle: t(i18n)`Wallet accounts`,
headerRight: () => (
<Link href="/wallet/new-account" asChild>
<Button size='icon' variant='ghost'>
<PlusIcon className='size-6 text-primary' />
</Button>
</Link>
)
}}
/>
<Stack.Screen
name="wallet/new-account"
options={{
headerShown: true,
headerBackTitleVisible: false,
headerTintColor: theme[colorScheme ?? 'light'].primary,
headerTitle: t(i18n)`Appearance`,
headerShadowVisible: false,
headerTitleStyle: {
fontFamily: 'Be Vietnam Pro Medium',
fontSize: 16,
color: theme[colorScheme ?? 'light'].primary,
},
headerStyle: {
backgroundColor: theme[colorScheme ?? 'light'].background,
}
// presentation: 'modal',
headerTitle: t(i18n)`New account`
}}
/>
</Stack>
Expand Down
16 changes: 10 additions & 6 deletions apps/mobile/app/(app)/language.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { MenuItem } from '@/components/common/menu-item'
import { Text } from '@/components/ui/text'
import { useLocale } from '@/locales/provider'
import { t } from '@lingui/macro'
import { useLingui } from '@lingui/react'
Expand All @@ -14,20 +13,25 @@ export default function LanguageScreen() {

return (
<ScrollView className="bg-card">
<Text className="font-medium text-lg m-6 mx-auto">
{t(i18n)`Language`}
</Text>
<MenuItem
label={t(i18n)`English`}
rightSection={language === 'en' && <CheckCircleIcon className='w-5 h-5 text-primary' />}
rightSection={
language === 'en' && (
<CheckCircleIcon className="w-5 h-5 text-primary" />
)
}
onPress={() => {
setLanguage('en')
router.back()
}}
/>
<MenuItem
label={t(i18n)`Vietnamese`}
rightSection={language === 'vi' && <CheckCircleIcon className='w-5 h-5 text-primary' />}
rightSection={
language === 'vi' && (
<CheckCircleIcon className="w-5 h-5 text-primary" />
)
}
onPress={() => {
setLanguage('vi')
router.back()
Expand Down
42 changes: 42 additions & 0 deletions apps/mobile/app/(app)/wallet/accounts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { AddNewButton } from '@/components/common/add-new-button'
import { Text } from '@/components/ui/text'
import { WalletAccountItem } from '@/components/wallet/wallet-account-item'
import { useWallets } from '@/queries/wallet'
import { t } from '@lingui/macro'
import { useLingui } from '@lingui/react'
import { useRouter } from 'expo-router'
import { FlatList } from 'react-native'

export default function WalletAccountsScreen() {
const { i18n } = useLingui()
const { data: walletAccounts, isLoading, refetch } = useWallets()
const router = useRouter()
return (
<FlatList
className="bg-card"
contentContainerClassName="py-3"
data={walletAccounts}
renderItem={({ item }) => (
<WalletAccountItem
// Date is typed as string in rpc output, not sure why
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
data={item as any}
/>
)}
keyExtractor={(item) => item.id}
refreshing={isLoading}
onRefresh={refetch}
ListFooterComponent={
<AddNewButton
label={t(i18n)`New account`}
onPress={() => router.push('/wallet/new-account')}
/>
}
ListEmptyComponent={
<Text className="font-sans text-muted-foreground text-center mt-6 mb-9">
{t(i18n)`empty`}
</Text>
}
/>
)
}
62 changes: 62 additions & 0 deletions apps/mobile/app/(app)/wallet/new-account.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { AccountForm } from '@/components/wallet/account-form'
import { createWallet } from '@/mutations/wallet'
import { walletQueries } from '@/queries/wallet'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { useRouter } from 'expo-router'
import { Alert, ScrollView } from 'react-native'

export default function NewAccountScreen() {
const queryClient = useQueryClient()
const router = useRouter()
const { mutateAsync } = useMutation({
mutationFn: createWallet,
async onMutate(newWallet) {
// Cancel any outgoing refetches
// (so they don't overwrite our optimistic update)
await queryClient.cancelQueries({
queryKey: walletQueries.list().queryKey,
})
// Snapshot the previous value
const previousWallets = queryClient.getQueryData(
walletQueries.list().queryKey,
)
// Optimistically update to the new value
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
queryClient.setQueryData(walletQueries.list().queryKey, (old: any) => [
...old,
newWallet,
])
// Return a context object with the snapshotted value
return { previousWallets }
},
onError(error, _, context) {
Alert.alert(error.message)
// use the context returned from onMutate to rollback
queryClient.setQueryData(
walletQueries.list().queryKey,
context?.previousWallets,
)
},
onSuccess() {
router.back()
},
async onSettled() {
// Always refetch after error or success:
await queryClient.invalidateQueries({
queryKey: walletQueries._def,
})
},
throwOnError: true,
})

return (
<ScrollView
className="bg-card flex-1"
contentContainerClassName="gap-4 p-6"
automaticallyAdjustKeyboardInsets
keyboardShouldPersistTaps="handled"
>
<AccountForm onSubmit={mutateAsync} />
</ScrollView>
)
}
45 changes: 45 additions & 0 deletions apps/mobile/app/(aux)/_layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { BackButton } from '@/components/common/back-button'
import { useColorScheme } from '@/hooks/useColorScheme'
import { theme } from '@/lib/theme'
import { t } from '@lingui/macro'
import { useLingui } from '@lingui/react'
import { Stack } from 'expo-router'

export default function AuxiliaryLayout() {
const { colorScheme } = useColorScheme()
const { i18n } = useLingui()
return (
<Stack
screenOptions={{
headerShown: true,
headerBackTitleVisible: false,
headerTintColor: theme[colorScheme ?? 'light'].primary,
headerShadowVisible: false,
headerTitleStyle: {
fontFamily: 'Be Vietnam Pro Medium',
fontSize: 16,
color: theme[colorScheme ?? 'light'].primary,
},
headerStyle: {
backgroundColor: theme[colorScheme ?? 'light'].background,
},
headerLeft: () => <BackButton />,
}}
>
<Stack.Screen
name="privacy-policy"
options={{
presentation: 'modal',
headerTitle: t(i18n)`Privacy Policy`,
}}
/>
<Stack.Screen
name="terms-of-service"
options={{
presentation: 'modal',
headerTitle: t(i18n)`Terms & Conditions`,
}}
/>
</Stack>
)
}
6 changes: 5 additions & 1 deletion apps/mobile/app/(aux)/privacy-policy.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { Text } from 'react-native'

export default function PrivacyScreen() {
return <Text className="font-sans m-4 mx-auto">Privacy Policy</Text>
return (
<Text className="font-sans m-4">
lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</Text>
)
}
4 changes: 3 additions & 1 deletion apps/mobile/app/(aux)/terms-of-service.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { Text } from 'react-native'

export default function TermsScreen() {
return (
<Text className="font-sans m-4 mx-auto">Terms & Conditions</Text>
<Text className="font-sans m-4">
lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</Text>
)
}
34 changes: 17 additions & 17 deletions apps/mobile/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,22 @@ import {
useFonts,
} from '@expo-google-fonts/be-vietnam-pro'
import { SplashScreen, Stack } from 'expo-router'
import * as WebBrowser from "expo-web-browser";
import * as WebBrowser from 'expo-web-browser'

import 'react-native-reanimated'
import { useWarmUpBrowser } from '@/hooks/use-warm-up-browser'
import { useColorScheme } from '@/hooks/useColorScheme'
import { queryClient } from '@/lib/client'
import { LocaleProvider } from '@/locales/provider'
import { BottomSheetModalProvider } from '@gorhom/bottom-sheet'
import {
DarkTheme,
DefaultTheme,
ThemeProvider,
} from '@react-navigation/native'
import { QueryClientProvider } from '@tanstack/react-query'
import { cssInterop } from 'nativewind'
import { GestureHandlerRootView } from 'react-native-gesture-handler'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { Svg } from 'react-native-svg'

Expand All @@ -38,15 +40,15 @@ cssInterop(Svg, {
// Prevent the splash screen from auto-hiding before asset loading is complete.
SplashScreen.preventAutoHideAsync()

WebBrowser.maybeCompleteAuthSession();
WebBrowser.maybeCompleteAuthSession()

// biome-ignore lint/style/useNamingConvention: <explanation>
export const unstable_settings = {
initialRouteName: '(app)',
}

export default function RootLayout() {
useWarmUpBrowser();
useWarmUpBrowser()
const { colorScheme } = useColorScheme()
const [fontsLoaded] = useFonts({
BeVietnamPro_300Light,
Expand All @@ -73,20 +75,18 @@ export default function RootLayout() {
value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}
>
<SafeAreaProvider>
<Stack screenOptions={{ headerShown: false }}>
<Stack.Screen
name="(aux)/privacy-policy"
options={{
presentation: 'modal',
}}
/>
<Stack.Screen
name="(aux)/terms-of-service"
options={{
presentation: 'modal',
}}
/>
</Stack>
<GestureHandlerRootView>
<BottomSheetModalProvider>
<Stack screenOptions={{ headerShown: false }}>
<Stack.Screen
name="(aux)"
options={{
presentation: 'modal',
}}
/>
</Stack>
</BottomSheetModalProvider>
</GestureHandlerRootView>
</SafeAreaProvider>
</ThemeProvider>
</LocaleProvider>
Expand Down
Loading