Skip to content

Commit

Permalink
feat(mobile): guard features base on user entitlement
Browse files Browse the repository at this point in the history
  • Loading branch information
bkdev98 committed Sep 7, 2024
1 parent 3723407 commit 7692807
Show file tree
Hide file tree
Showing 13 changed files with 177 additions and 57 deletions.
21 changes: 11 additions & 10 deletions apps/api/v1/constants/wallet.const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ export const DEFAULT_WALLETS = [
vi: 'Thẻ tín dụng',
icon: 'CreditCard',
},
{
en: 'Investment',
vi: 'Đầu tư',
icon: 'ChartLine',
},
{
en: 'Other Wallet',
vi: 'Ví khác',
icon: 'Banknote',
},
// Limit to 3 wallets for free users
// {
// en: 'Investment',
// vi: 'Đầu tư',
// icon: 'ChartLine',
// },
// {
// en: 'Other Wallet',
// vi: 'Ví khác',
// icon: 'Banknote',
// },
]
17 changes: 5 additions & 12 deletions apps/mobile/app/(app)/(tabs)/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import { TabBar } from '@/components/common/tab-bar'
import { Button } from '@/components/ui/button'
import { Text } from '@/components/ui/text'
import { useColorScheme } from '@/hooks/useColorScheme'
import { theme } from '@/lib/theme'
import { useUser } from '@clerk/clerk-expo'
import { t } from '@lingui/macro'
import { useLingui } from '@lingui/react'
import { Link, Tabs } from 'expo-router'
import { PlusIcon } from 'lucide-react-native'
import { Tabs } from 'expo-router'
import { usePostHog } from 'posthog-react-native'
import { useEffect } from 'react'
import { useWindowDimensions } from 'react-native'
Expand All @@ -32,6 +29,10 @@ export default function TabLayout() {
name: user?.fullName,
})
Purchases.logIn(user.id)
Purchases.setAttributes({
email: user?.emailAddresses?.[0]?.emailAddress,
displayName: user?.fullName,
})
}, [user, posthog])

return (
Expand Down Expand Up @@ -78,14 +79,6 @@ export default function TabLayout() {
headerTitleStyle: {
marginLeft: 5,
},
headerRight: () => (
<Link href="/budget/new-budget" asChild>
<Button size="sm" variant="secondary" className="mr-6 h-10">
<PlusIcon className="size-6 text-primary" />
<Text>{t(i18n)`New budget`}</Text>
</Button>
</Link>
),
headerTitleAlign: 'left',
}}
/>
Expand Down
26 changes: 26 additions & 0 deletions apps/mobile/app/(app)/(tabs)/budgets.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { BudgetItem } from '@/components/budget/budget-item'
import { BudgetStatistic } from '@/components/budget/budget-statistic'
import { BurndownChart } from '@/components/budget/burndown-chart'
import { Button } from '@/components/ui/button'
// import { Toolbar } from '@/components/common/toolbar'
import { Skeleton } from '@/components/ui/skeleton'
import { Text } from '@/components/ui/text'
import { useUserEntitlements } from '@/hooks/use-purchases'
import { useColorScheme } from '@/hooks/useColorScheme'
import { ENTILEMENT_LIMIT } from '@/lib/constaints'
import { theme } from '@/lib/theme'
import { useBudgetList } from '@/stores/budget/hooks'
import { useTransactionList } from '@/stores/transaction/hooks'
Expand All @@ -13,7 +16,10 @@ import type { Budget, BudgetPeriodConfig } from '@6pm/validation'
import { t } from '@lingui/macro'
import { useLingui } from '@lingui/react'
import { LinearGradient } from 'expo-linear-gradient'
import { Link, useNavigation } from 'expo-router'
import { groupBy, map } from 'lodash-es'
import { PlusIcon } from 'lucide-react-native'
import { useEffect } from 'react'
import { Dimensions, SectionList, View } from 'react-native'
import Animated, {
Extrapolation,
Expand Down Expand Up @@ -45,6 +51,7 @@ export default function BudgetsScreen() {
const headerAnimation = useSharedValue(0)
const scrollY = useSharedValue(0)
const headerHeight = useSharedValue(height)
const navigation = useNavigation()

const dummyHeaderStyle = useAnimatedStyle(() => {
return {
Expand Down Expand Up @@ -137,6 +144,25 @@ export default function BudgetsScreen() {
refetch,
} = useBudgetList()

const { entilement } = useUserEntitlements()

const isExceeded =
ENTILEMENT_LIMIT[entilement]?.wallets <= (spendingBudgets?.length ?? 0)

// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
useEffect(() => {
navigation.setOptions({
headerRight: () => (
<Link href={isExceeded ? '/paywall' : '/budget/new-budget'} asChild>
<Button size="sm" variant="secondary" className="mr-6 h-10">
<PlusIcon className="size-6 text-primary" />
<Text>{t(i18n)`New budget`}</Text>
</Button>
</Link>
),
})
}, [isExceeded])

const { transactions } = useTransactionList({
from: dayjsExtended().startOf('month').toDate(),
to: dayjsExtended().endOf('month').toDate(),
Expand Down
18 changes: 1 addition & 17 deletions apps/mobile/app/(app)/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { AuthLocal } from '@/components/auth/auth-local'
import { BackButton } from '@/components/common/back-button'
import { Button } from '@/components/ui/button'
import { useLocalAuth } from '@/hooks/use-local-auth'
import { useScheduleNotificationTrigger } from '@/hooks/use-schedule-notification'
import { useUserMetadata } from '@/hooks/use-user-metadata'
Expand All @@ -9,8 +8,7 @@ import { theme } from '@/lib/theme'
import { useUser } from '@clerk/clerk-expo'
import { t } from '@lingui/macro'
import { useLingui } from '@lingui/react'
import { Link, Redirect, SplashScreen, Stack } from 'expo-router'
import { PlusIcon } from 'lucide-react-native'
import { Redirect, SplashScreen, Stack } from 'expo-router'
import { useEffect } from 'react'
import { View } from 'react-native'

Expand Down Expand Up @@ -128,13 +126,6 @@ export default function AuthenticatedLayout() {
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
Expand All @@ -155,13 +146,6 @@ export default function AuthenticatedLayout() {
name="category/index"
options={{
headerTitle: t(i18n)`Categories`,
headerRight: () => (
<Link href="/category/new-category" asChild>
<Button size="icon" variant="ghost">
<PlusIcon className="size-6 text-primary" />
</Button>
</Link>
),
}}
/>
<Stack.Screen
Expand Down
5 changes: 4 additions & 1 deletion apps/mobile/app/(app)/category/[categoryId].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { CategoryForm } from '@/components/category/category-form'
import { toast } from '@/components/common/toast'
import { Button } from '@/components/ui/button'
import { Text } from '@/components/ui/text'
import { useUserEntitlements } from '@/hooks/use-purchases'
import {
useCategory,
useDeleteCategory,
Expand All @@ -20,6 +21,7 @@ export default function EditCategoryScreen() {
const { category } = useCategory(categoryId!)
const navigation = useNavigation()
const { i18n } = useLingui()
const { isPro } = useUserEntitlements()

const { mutateAsync: mutateUpdate } = useUpdateCategory()
const { mutateAsync: mutateDelete } = useDeleteCategory()
Expand All @@ -31,6 +33,7 @@ export default function EditCategoryScreen() {
<Button
size="icon"
variant="ghost"
disabled={!isPro}
onPress={() =>
Alert.alert(
t(
Expand Down Expand Up @@ -59,7 +62,7 @@ export default function EditCategoryScreen() {
</Button>
),
})
}, [])
}, [isPro])

if (!category) {
return (
Expand Down
25 changes: 22 additions & 3 deletions apps/mobile/app/(app)/category/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { CategoryItem } from '@/components/category/category-item'
import { AddNewButton } from '@/components/common/add-new-button'
import { Button } from '@/components/ui/button'
import { Skeleton } from '@/components/ui/skeleton'
import { Text } from '@/components/ui/text'
import { useUserEntitlements } from '@/hooks/use-purchases'
import { useCategoryList } from '@/stores/category/hooks'
import { t } from '@lingui/macro'
import { useLingui } from '@lingui/react'
import { useRouter } from 'expo-router'
import { Link, useNavigation, useRouter } from 'expo-router'
import { PlusIcon } from 'lucide-react-native'
import { useEffect } from 'react'
import { SectionList } from 'react-native'
import { useSafeAreaInsets } from 'react-native-safe-area-context'

Expand All @@ -15,6 +19,21 @@ export default function CategoriesScreen() {
const { incomeCategories, expenseCategories, isRefetching, refetch } =
useCategoryList()
const { bottom } = useSafeAreaInsets()
const { isPro } = useUserEntitlements()
const navigation = useNavigation()

// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
useEffect(() => {
navigation.setOptions({
headerRight: () => (
<Link href={isPro ? '/category/new-category' : '/paywall'} asChild>
<Button size="icon" variant="ghost">
<PlusIcon className="size-6 text-primary" />
</Button>
</Link>
),
})
}, [isPro])

const sections = [
{ key: 'INCOME', title: t(i18n)`Incomes`, data: incomeCategories },
Expand All @@ -25,7 +44,7 @@ export default function CategoriesScreen() {
<SectionList
className="flex-1 bg-card"
contentContainerStyle={{ paddingBottom: bottom }}
refreshing={isRefetching}
refreshing={false}
onRefresh={refetch}
sections={sections}
keyExtractor={(item) => item.id}
Expand All @@ -51,7 +70,7 @@ export default function CategoriesScreen() {
label={t(i18n)`New ${section.key.toLowerCase()}`}
onPress={() =>
router.push({
pathname: '/category/new-category',
pathname: isPro ? '/category/new-category' : '/paywall',
params: { type: section.key },
})
}
Expand Down
4 changes: 4 additions & 0 deletions apps/mobile/app/(app)/transaction/new-record.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,13 @@ export default function NewRecordScreen() {
<View style={{ width }}>
<Scanner
onScanStart={() => {
router.back()
toast.success(t(i18n)`Transaction added to processing queue`)
}}
shouldRender={page === 1}
onLimitExceeded={() => {
router.push('/paywall')
}}
/>
</View>
</ScrollView>
Expand Down
32 changes: 30 additions & 2 deletions apps/mobile/app/(app)/wallet/accounts.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,42 @@
import { AddNewButton } from '@/components/common/add-new-button'
import { Button } from '@/components/ui/button'
import { Skeleton } from '@/components/ui/skeleton'
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 { t } from '@lingui/macro'
import { useLingui } from '@lingui/react'
import { useRouter } from 'expo-router'
import { Link } from 'expo-router'
import { useNavigation, useRouter } from 'expo-router'
import { PlusIcon } from 'lucide-react-native'
import { useEffect } from 'react'
import { FlatList } from 'react-native'

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

const isExceeded =
ENTILEMENT_LIMIT[entilement]?.wallets <= (walletAccounts?.length ?? 0)

// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
useEffect(() => {
navigation.setOptions({
headerRight: () => (
<Link href={isExceeded ? '/paywall' : '/wallet/new-account'} asChild>
<Button size="icon" variant="ghost">
<PlusIcon className="size-6 text-primary" />
</Button>
</Link>
),
})
}, [isExceeded])

return (
<FlatList
className="bg-card"
Expand All @@ -21,10 +46,13 @@ export default function WalletAccountsScreen() {
keyExtractor={(item) => item.id}
refreshing={isLoading}
onRefresh={refetch}
extraData={[isExceeded]}
ListFooterComponent={
<AddNewButton
label={t(i18n)`New account`}
onPress={() => router.push('/wallet/new-account')}
onPress={() =>
router.push(isExceeded ? '/paywall' : '/wallet/new-account')
}
/>
}
ListEmptyComponent={
Expand Down
Loading

0 comments on commit 7692807

Please sign in to comment.