1- import React from 'react' ;
2- import { Text } from 'react-native' ;
3- import { NavigationContainer } from '@react-navigation/native' ;
1+ import React , { useCallback } from 'react' ;
2+ import { ActivityIndicator , Text , View } from 'react-native' ;
3+ import {
4+ NavigationContainer ,
5+ LinkingOptions ,
6+ getStateFromPath ,
7+ NavigationState ,
8+ PartialState ,
9+ Route ,
10+ } from '@react-navigation/native' ;
411import { navigationRef } from './navigationRef' ;
512import { createBottomTabNavigator } from '@react-navigation/bottom-tabs' ;
613import { createNativeStackNavigator } from '@react-navigation/native-stack' ;
@@ -12,6 +19,10 @@ import { darkNavigationTheme, lightNavigationTheme } from '../theme/navigationTh
1219
1320import HomeScreen from '../screens/HomeScreen' ;
1421import { SettingsScreen } from '../screens/SettingsScreen' ;
22+ import { useUserStore } from '../store/userStore' ;
23+ import { FeatureId } from '../types/feature' ;
24+ import { featureFlagsService } from '../services/featureFlags' ;
25+ import type { SubscriptionTier } from '../types/subscription' ;
1526
1627const AddSubscriptionScreen = lazyScreen ( ( ) => import ( '../screens/AddSubscriptionScreen' ) ) ;
1728const CancellationFlowScreen = lazyScreen ( ( ) => import ( '../screens/CancellationFlowScreen' ) ) ;
@@ -107,6 +118,157 @@ const DunningDashboardScreen = lazyScreen(() => import('../screens/DunningDashbo
107118const Tab = createBottomTabNavigator < TabParamList > ( ) ;
108119const Stack = createNativeStackNavigator < RootStackParamList > ( ) ;
109120
121+ const routeFeatureMap : Partial < Record < keyof RootStackParamList , FeatureId > > = {
122+ CryptoPayment : FeatureId . CRYPTO_INTEGRATION ,
123+ Analytics : FeatureId . ADVANCED_ANALYTICS ,
124+ Export : FeatureId . EXPORT_DATA ,
125+ DeveloperPortal : FeatureId . DEVELOPER_PORTAL ,
126+ SandboxDashboard : FeatureId . SANDBOX_ACCESS ,
127+ ApiKeyManagement : FeatureId . API_ACCESS ,
128+ } ;
129+
130+ const authRequiredRoutes : Set < keyof RootStackParamList > = new Set ( [
131+ 'Profile' ,
132+ 'AdminDashboard' ,
133+ 'ApiKeyManagement' ,
134+ 'DeveloperPortal' ,
135+ 'SandboxDashboard' ,
136+ 'MerchantOnboarding' ,
137+ 'AffiliateDashboard' ,
138+ 'LoyaltyDashboard' ,
139+ 'CampaignManagement' ,
140+ ] ) ;
141+
142+ const requiredParamsByRoute : Partial < Record < keyof RootStackParamList , string [ ] > > = {
143+ SubscriptionDetail : [ 'id' ] ,
144+ CancellationFlow : [ 'subscriptionId' ] ,
145+ InvoiceDetail : [ 'id' ] ,
146+ SegmentDetail : [ 'segmentId' ] ,
147+ } ;
148+
149+ const getActiveRoute = (
150+ route : Route < string , object | undefined > | undefined
151+ ) : Route < string , object | undefined > | undefined => {
152+ if ( ! route || ! ( 'state' in route ) || ! route . state || ! Array . isArray ( route . state . routes ) ) {
153+ return route ;
154+ }
155+
156+ const nested = route . state . routes [ route . state . index ?? 0 ] as Route < string , object | undefined > ;
157+ return getActiveRoute ( nested ) ;
158+ } ;
159+
160+ const hasValidRequiredParams = ( route : Route < string , object | undefined > | undefined ) : boolean => {
161+ if ( ! route ) return false ;
162+ const expected = requiredParamsByRoute [ route . name as keyof RootStackParamList ] ;
163+ if ( ! expected ) return true ;
164+
165+ const params = route . params as Record < string , unknown > | undefined ;
166+ return expected . every ( ( key ) => typeof params ?. [ key ] === 'string' && params ?. [ key ] ) ;
167+ } ;
168+
169+ const getStateFromPathSafe = ( path : string , options ?: any ) => {
170+ const state = getStateFromPath ( path , options ) ;
171+ if ( ! state || ! state . routes ?. length ) return undefined ;
172+
173+ const activeRoute = getActiveRoute ( state . routes [ state . index ?? 0 ] as Route < string , object | undefined > ) ;
174+ if ( ! hasValidRequiredParams ( activeRoute ) ) return undefined ;
175+
176+ return state ;
177+ } ;
178+
179+ const isRouteAllowed = (
180+ route : Route < string , object | undefined > | undefined ,
181+ isAuthenticated : boolean ,
182+ subscriptionTier : SubscriptionTier
183+ ) : boolean => {
184+ if ( ! route ) return false ;
185+
186+ if ( authRequiredRoutes . has ( route . name as keyof RootStackParamList ) && ! isAuthenticated ) {
187+ return false ;
188+ }
189+
190+ const featureId = routeFeatureMap [ route . name as keyof RootStackParamList ] ;
191+ if ( featureId ) {
192+ const feature = featureFlagsService . getFeature ( featureId ) ;
193+ if ( ! feature || ! feature . enabled ) {
194+ return false ;
195+ }
196+
197+ if ( ! feature . tierAccess . includes ( subscriptionTier ) ) {
198+ return false ;
199+ }
200+ }
201+
202+ return true ;
203+ } ;
204+
205+ const linking : LinkingOptions < TabParamList > = {
206+ prefixes : [ 'subtrackr://' , 'https://subtrackr.app' ] ,
207+ config : {
208+ screens : {
209+ HomeTab : {
210+ path : '' ,
211+ screens : {
212+ Home : 'home' ,
213+ AddSubscription : 'subscriptions/add' ,
214+ SubscriptionDetail : 'subscriptions/:id' ,
215+ CancellationFlow : 'subscriptions/:subscriptionId/cancel' ,
216+ WalletConnect : 'wallet/connect' ,
217+ CryptoPayment : 'crypto-payment/:subscriptionId?' ,
218+ Community : 'community' ,
219+ Profile : 'profile/:subscriber?' ,
220+ Analytics : 'analytics' ,
221+ SlaDashboard : 'sla' ,
222+ InvoiceList : 'invoices' ,
223+ InvoiceDetail : 'invoices/:id' ,
224+ GDPRSettings : 'settings/privacy' ,
225+ LanguageSettings : 'settings/language' ,
226+ ErrorDashboard : 'errors' ,
227+ SegmentManagement : 'segments' ,
228+ SegmentDetail : 'segments/:segmentId' ,
229+ Gamification : 'gamification' ,
230+ FraudDashboard : 'fraud' ,
231+ GroupManagement : 'groups' ,
232+ SupportDashboard : 'support' ,
233+ UsageDashboard : 'usage/:subscriptionId?/:planId?/:name?' ,
234+ DeveloperPortal : 'developer' ,
235+ SandboxDashboard : 'sandbox' ,
236+ ApiKeyManagement : 'api-keys' ,
237+ DocumentationPortal : 'docs' ,
238+ IntegrationGuides : 'integration-guides' ,
239+ } ,
240+ } ,
241+ AddTab : 'add' ,
242+ WalletTab : 'wallet' ,
243+ AnalyticsTab : 'analytics' ,
244+ RevenueTab : 'revenue' ,
245+ SettingsTab : {
246+ path : 'settings' ,
247+ screens : {
248+ Settings : '' ,
249+ CalendarIntegration : 'calendar' ,
250+ WebhookSettings : 'webhooks' ,
251+ AccountingExport : 'accounting' ,
252+ BatchOperations : 'batch' ,
253+ AdminDashboard : 'admin' ,
254+ FraudDashboard : 'fraud' ,
255+ TaxSettings : 'tax' ,
256+ SupportDashboard : 'support' ,
257+ GroupManagement : 'groups' ,
258+ MerchantOnboarding : 'merchant-onboarding' ,
259+ AffiliateDashboard : 'affiliate' ,
260+ LoyaltyDashboard : 'loyalty' ,
261+ CampaignManagement : 'campaigns' ,
262+ DeveloperPortal : 'developer' ,
263+ DocumentationPortal : 'docs' ,
264+ ApiKeyManagement : 'api-keys' ,
265+ } ,
266+ } ,
267+ } ,
268+ } ,
269+ getStateFromPath : getStateFromPathSafe ,
270+ } ;
271+
110272const HomeStack = ( ) => (
111273 < Stack . Navigator >
112274 < Stack . Screen name = "Home" component = { HomeScreen } options = { { headerShown : false } } />
@@ -281,11 +443,6 @@ const SettingsStack = () => (
281443 component = { LanguageSettingsScreen }
282444 options = { { title : 'Language' , headerShown : true } }
283445 />
284- < Stack . Screen
285- name = "Export"
286- component = { ExportScreen }
287- options = { { title : 'Export' , headerShown : true } }
288- />
289446 < Stack . Screen
290447 name = "BatchOperations"
291448 component = { BatchOperationsScreen }
@@ -549,11 +706,32 @@ export const AppNavigator = () => {
549706 prefetchModule ( 'SubscriptionDetail' , ( ) => import ( '../screens/SubscriptionDetailScreen' ) ) ;
550707 } , [ ] ) ;
551708
709+ const user = useUserStore ( ( state ) => state . user ) ;
710+ const subscriptionTier = useUserStore ( ( state ) => state . subscriptionTier ) ;
552711 const { isDark } = useTheme ( ) ;
553712
713+ const handleStateChange = useCallback (
714+ ( state ?: PartialState < NavigationState > | undefined ) => {
715+ if ( ! state ) return ;
716+ const activeRoute = getActiveRoute ( state . routes [ state . index ?? 0 ] as Route < string , object | undefined > ) ;
717+ const isAuthenticated = Boolean ( user ) ;
718+ if ( ! isRouteAllowed ( activeRoute , isAuthenticated , subscriptionTier ) ) {
719+ console . warn (
720+ `Blocked navigation to ${ activeRoute ?. name } . Falling back to HomeTab due to auth/feature gating.`
721+ ) ;
722+ if ( navigationRef . isReady ( ) ) {
723+ navigationRef . reset ( { index : 0 , routes : [ { name : 'HomeTab' } ] } ) ;
724+ }
725+ }
726+ } ,
727+ [ subscriptionTier , user ]
728+ ) ;
729+
554730 return (
555731 < NavigationContainer
556732 ref = { navigationRef }
733+ linking = { linking }
734+ onStateChange = { handleStateChange }
557735 theme = { isDark ? darkNavigationTheme : lightNavigationTheme } >
558736 < TabNavigator />
559737 </ NavigationContainer >
0 commit comments