Skip to content

Instantly share code, notes, and snippets.

@sahilkashyap64
Last active June 14, 2024 13:39
Show Gist options
  • Save sahilkashyap64/b48e563b8ab3bbf247c40700622e7b57 to your computer and use it in GitHub Desktop.
Save sahilkashyap64/b48e563b8ab3bbf247c40700622e7b57 to your computer and use it in GitHub Desktop.
Deeplinking with authentication react native recoil
import {atom} from 'recoil';
import { Player,ScreenNames, LoginScreen, SignupScreen, ForgotPasswordScreen, ResetPasswordScreen } from '../screens';
export const userState=atom({
key:"userSession",
default:{
session : null,
userType : null,
onboarding : false,
profile:null,
err:null,
loading: true,
locations: [],
}
})
export const upcomingPlayerLessonState = atom({
key:"upComingPlayerLesson",
default:{
data:[],
err:null
}
})
export const playerCoachesState = atom({
key:"playerCoaches",
default:{
data:[],
err:null
}
})
export const playerCoachesLessonsState = atom({
key:"playerCoachesLessons",
default:{
data:[],
err:null
}
})
export const playerCoachesLocationState = atom({
key:"playerCoachesLocation",
default:{
data:[],
err:null
}
})
export const playerFilterCoachesScheduleState = atom({
key:"playerFilterCoachesScheduleState",
default:{
data:[],
err:null
}
})
export const playerCoachScheduleHistoryState = atom({
key:"playerCoachScheduleHistory",
default:{
data:[],
err:null
}
})
export const coachStatusState = atom({
key:"coachStatus",
default:{
status:null,
err:null
}
})
export const playerCoachScheduleState = atom({
key:"playerCoachSchedule",
default:{
data:[],
err:null
}
})
export const notificationState = atom({
key:'notification',
default:{
data:[],
err:null
}
})
export const upcomingCoachLessonState = atom({
key:"upComingCoachLesson",
default:{
data:[],
err:null
}
})
export const upcomingCoachLessonByDateState = atom({
key:"upComingCoachByDateLesson",
default:{
data:[],
err:null
}
})
export const coachPlayersListState = atom({
key:"coachPlayersList",
default:{
data:[],
err:null
}
})
export const coachAllPlayersListState = atom({
key:"coachAllPlayersList",
default:{
data:[],
err:null
}
})
export const singleCoachPlayerState = atom({
key:"singleCoachPlayer",
default:{
data:null,
err:null
}
})
export const particularPlayerUpComingCoachLessonState = atom({
key:"particularPlayerComingCoachLesson",
default:{
data:[],
err:null
}
})
export const particularPlayerPreviousCoachLessonState = atom({
key:"particularPlayerPreviousCoachLesson",
default:{
data:[],
err:null
}
})
export const coachAccountBalanceState = atom({
key:"coachAccountBalance",
default:{
data:null,
err:null
}
})
export const coachAccountHistoryState = atom({
key:"coachAccountHistory",
default:{
data:[],
err:null
}
})
export const coachLocationState = atom({
key:"coachLocation",
default:{
data:[],
err:null
}
})
export const coachScheduleState = atom({
key:"coachSchedule",
default:{
data:[],
err:null
}
})
export const coachAllScheduleState = atom({
key:"coachAllSchedule",
default:{
data:[],
err:null
}
})
export const coachAvailabilityState = atom({
key:"coachAvailability",
default:{
sunday: [],
monday: [],
tuesday: [],
wednesday: [],
thursday: [],
friday: [],
saturday: []
}
})
export const productsState = atom({
key: `productsState`,
default: [],
})
export const collectionState = atom({
key: `collectionState`,
default: [],
})
export const cartState = atom({
key: 'cartState',
default: [],
})
export const stripePaymentMethodState = atom({
key: 'stripePaymentMethod',
default: [],
});
export const surveyQuestionsState = atom({
key: 'surveyQuestions',
default: [],
});
export const availablePlayersState = atom({
key: 'availablePlayersState', // Unique ID (with respect to other atoms/selectors)
default: {
data: [],
err: null,
}, // Default value (aka initial value)
});
export const surveyQuestionsAnsweredState = atom({
key: 'surveyQuestionsAnsweredState', // Unique ID (with respect to other atoms/selectors)
default: [], // Default value (aka initial value), assuming an array of questions and answers
});
export const hasUnansweredQuestionsState = atom({
key: 'hasUnansweredQuestionsState', // unique ID (with respect to other atoms/selectors)
default: true, // default value (aka initial state)
});
export const navigationState = atom({
key: 'navigationState',
default: [
{ name: ScreenNames.LOGIN, component: LoginScreen, condition: true },
{ name: ScreenNames.SIGNUP, component: SignupScreen, condition: true },
{ name: ScreenNames.FORGOT_PASSWORD, component: ForgotPasswordScreen, condition: true },
{ name: ScreenNames.RESET_PASSWORD, component: ResetPasswordScreen, condition: true },
{ name: ScreenNames.PLAYER, component: Player, condition: true }
], // Default to an empty array
});
//list of screens accessible by deeplink
import { ScreenNames } from "../../screens/index";
const config = {
screens: {
PublicStack: {
initialRouteName: 'Login',
screens: {
Login: 'login',
Signup: 'signup',
},
},
PrivateStack: {
initialRouteName: 'Home',
screens: {
Player: {
initialRouteName: 'PlayerHomeStack',
screens: {
PlayerHomeStack: {
initialRouteName: ScreenNames.PLAYER_DASHBOARD,
screens: {
PlayerDashboard: {
path: 'player/lesson/:id',
parse: { id: Number },
},
SuggestPlayerDetailScreen: {
path: 'player/profile/:userId',
parse: { userId: String },
},
// Add other nested screens if necessary
},
},
PlayerCalendarStack: {
initialRouteName: ScreenNames.PLAYER_CALENDAR_STACK,
screens: {
PlayerCalendar: 'calendar',
// Add other nested screens if necessary
},
},
PlayerNotificationStack: {
initialRouteName: ScreenNames.PLAYER_NOTIFICATION_STACK,
screens: {
PlayerNotificationScreen: 'notifications',
// Add other nested screens if necessary
},
},
PlayerProfileStack: {
initialRouteName: ScreenNames.PLAYER_PROFILE,
screens: {
PlayerProfile: 'profile',
// Add other nested screens if necessary
},
},
PlayerCoachStack: {
initialRouteName: ScreenNames.PLAYER_COACH_LIST,
screens: {
PlayerCoachList: 'coach_list',
// Add other nested screens if necessary
},
},
},
},
},
},
NotFound: '*',
},
};
export default config;
//global ref used,to acces recoil state globally
/**
* @format
*/
import {AppRegistry} from 'react-native';
import App from './src/App';
import Navigation from './src/navigation/index'
import {name as appName} from './app.json';
import React from 'react'
import {RecoilRoot} from 'recoil'
import {CacheManager} from '@georstat/react-native-image-cache'
import {Dirs} from 'react-native-file-access'
import UserStateHandler, { userGlobalRef } from './src/navigation/UserStateHandler';
CacheManager.config = {
baseDir: `${Dirs.CacheDir}/images_cache/`,
sourceAnimationDuration: 1000,
thumbnailAnimationDuration: 1000,
};
const TTPApp = () => {
return (
<RecoilRoot>
<UserStateHandler ref={userGlobalRef} />
<Navigation />
</RecoilRoot>
)
}
AppRegistry.registerComponent(appName, () => TTPApp);
//navigation index, deeplink imported
import React,{useEffect} from 'react';
import {Text,Platform} from 'react-native';
import {NavigationContainer} from '@react-navigation/native';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
import {initStripe} from '@stripe/stripe-react-native';
import Toast, {BaseToast, ErrorToast} from 'react-native-toast-message';
import PublicStack from './stacks/public';
import PrivateStack from './stacks/private';
import AppBoot from './appBoot';
import {
ScreenNames,
SplashScreen
} from '../screens/';
import VersionCheck from 'react-native-version-check';
import {checkVersion} from '../api/auth';
// import PrivacyPolicy from './screens/privacyPolicy';
import colors from '../theme/colors';
import Loader from '../components/Loaders/loader';
import {navigationRef} from './utils'; // navigation ref
import linking from './deeplink'; // deep linking configuration
const Stack = createNativeStackNavigator();
// This is a Root Navigation File. Use this file as a child of App.js
const Navigation = () => {
const publishableKey ='pk_test_xxxx';
useEffect(() => {
initStripe({ publishableKey });
checkForMinimalApp();
}, []);
const checkForMinimalApp = () => {
checkVersion().then(async response => {
const responseData = await response.json();
const VersionDetails = Platform.OS === 'android' ? responseData.android : responseData.ios;
VersionCheck.needUpdate({
currentVersion: VersionCheck.getCurrentVersion(),
latestVersion: VersionDetails.version,
}).then(res => {
if (res.isNeeded) {
const storeUrlDetails = VersionDetails.link;
Alert.alert("Update", 'Kindly update the app', [
{ text: 'Update', onPress: () => { BackHandler.exitApp(); Linking.openURL(storeUrlDetails); }},
], { cancelable: false });
}
});
}).catch(err => {
console.log("err Version check", err);
});
};
Text.defaultProps = {};
Text.defaultProps.allowFontScaling = false;
const toastConfig = {
success: props => (
<BaseToast
{...props}
style={{ borderLeftColor: colors.theme.bottleGreen }}
contentContainerStyle={{ backgroundColor: colors.theme.bottleGreen }}
text1Style={{ fontSize: 15, fontWeight: '400', color: 'white' }}
text2Style={{ fontSize: 12, fontWeight: '400', color: 'white' }}
/>
),
error: props => (
<ErrorToast
{...props}
style={{ borderLeftColor: colors.theme.red }}
contentContainerStyle={{ backgroundColor: colors.theme.red }}
text1Style={{ fontSize: 15, fontWeight: '400', color: 'white' }}
text2Style={{ fontSize: 12, fontWeight: '400', color: 'white' }}
/>
),
};
console.log("navigation splash,PublicStack,PrivateStack");
return (
<>
<NavigationContainer
ref={navigationRef}
linking={linking}
fallback={<Loader />}>
<Stack.Navigator screenOptions={{ headerShown: false }}>
{/* <Stack.Screen name="AppBoot" component={AppBoot} /> */}
<Stack.Screen name={ScreenNames.SPLASH} component={SplashScreen} />
<Stack.Screen name="PublicStack" component={PublicStack} />
<Stack.Screen name="PrivateStack" component={PrivateStack} />
{/* <Stack.Screen name="PrivacyPolicy" component={PrivacyPolicy} /> */}
</Stack.Navigator>
</NavigationContainer>
<Toast config={toastConfig} />
</>
);
};
export default Navigation;
//private stack
import React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { USER_TYPES } from '../../constants/';
import {
ScreenNames,
Player,
Coach,
CoachPaymentMethodPage,
CoachProfileEdit
} from '../../screens';
import { useRecoilValue } from 'recoil';
import { navigationState } from '../../store/atoms';
const Stack = createNativeStackNavigator();
const PrivateStack = () => {
const navigation = useRecoilValue(navigationState);
// console.log('PrivateStack navigation:', JSON.stringify(navigation));
return (
<Stack.Navigator screenOptions={{ headerShown: false }}>
{navigation.map((screen, index) => (
screen.condition && (
<Stack.Screen
key={index}
name={screen.name}
component={screen.component}
/>
)
))}
</Stack.Navigator>
);
};
export default PrivateStack;
//public stack
import React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import {
ScreenNames,
LoginScreen,
SignupScreen,
ForgotPasswordScreen,
ResetPasswordScreen,
SplashScreen
} from '../../screens/';
const Stack = createNativeStackNavigator();
const PublicStack = () => {
// console.log("PublicStack");
return (
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name={ScreenNames.LOGIN} component={LoginScreen} />
<Stack.Screen name={ScreenNames.SIGNUP} component={SignupScreen} />
<Stack.Screen name={ScreenNames.FORGOT_PASSWORD} component={ForgotPasswordScreen} />
<Stack.Screen name={ScreenNames.RESET_PASSWORD} component={ResetPasswordScreen} />
</Stack.Navigator>
);
};
export default PublicStack;
//restores session,and recoil state is updated,hack is used userGlobalRef
import AsyncStorage from '@react-native-async-storage/async-storage';
import { decode } from 'react-native-pure-jwt';
import { getProfile, refreshToken } from '../api/auth'; // axios request with middleware
import { userState } from '../store/atoms';
import { useSetRecoilState } from 'recoil';
import { AS_USER_KEY } from '../constants/urls';
import { userGlobalRef } from '../navigation/UserStateHandler'; // Import the ref
import { replaceStack } from '../navigation/utils';
export const restoreSession = async () => {
const sessionData = await AsyncStorage.getItem(AS_USER_KEY);
// console.log("restoreSession",sessionData);
if (!sessionData) {
console.log('πŸ”΄ 1.Session data is null');
// replaceStack('PublicStack');
throw new Error('Session data is null');
}
const parsedUserData = JSON.parse(sessionData);
const { session , loading} = parsedUserData;
if (!loading && !session) {
console.log('πŸ”΄ 2.Session not found');
// replaceStack('PublicStack');
throw new Error('Session not found');
}
// user.session.access_token
const jwt = await decode(
session.access_token, // the token
'my-token-secret', // the secret part
{ skipValidation: true }
);
console.log("jwt",jwt.payload.data);
console.log("πŸ€“ restoreSession parsedUserData",parsedUserData);
// Set user state using the ref
userGlobalRef.current?.setUser({ ...parsedUserData, loading: false });
return { ...parsedUserData, loading: false }
// return null;
// const setUser = useSetRecoilState(userState);
// setUser(sessionData);
// return JSON.parse(sessionData);
// // Check if previous JWT token expired
// if (Date.now() >= jwt.payload.exp * 1000) {
// const freshSessionData = await refreshToken({
// refreshToken: rest.refreshToken,
// });
// const newSessionData = JSON.stringify(freshSessionData.data);
// console.log("newSessionData",newSessionData);
// // return;
// await AsyncStorage.setItem(AS_USER_KEY, newSessionData);
// }
// const { data } = await getProfile(); // GET /profile_data
// // Use Recoil to set the profile state
// const setProfile = useSetRecoilState(profileState);
// setProfile(data);
};
// export const useRestoreSession = () => {
// const setUser = useSetRecoilState(userState);
// const restoreUserSession = async () => {
// const sessionData = await restoreSession();
// setUser(sessionData);
// };
// return restoreUserSession;
// };
//first screen in navigation, loads the user if in memory, and redirects to which stack to go
/* eslint-disable react-hooks/exhaustive-deps */
import React, {useEffect} from 'react';
import {Image, SafeAreaView} from 'react-native';
import internalStyle from './style';
import useAuth from '../../hooks/useAuth';
import useSession from '../../hooks/useSession';
const mainLogo = require('../../assets/images/logo.png');
const SplashScreen = ({navigation}) => {
useSession();
// const {getUserFromStorage} = useAuth();
// useEffect(() => {
// console.log("SplashScreen");
// getUserFromStorage();
// }, []);
return (
<SafeAreaView style={internalStyle.splashContainer}>
<Image source={mainLogo} style={internalStyle.splashLogo} />
</SafeAreaView>
);
};
export default SplashScreen;
import {useRecoilState} from 'recoil';
import {userState,navigationState} from '../store/atoms';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { AS_USER_KEY } from '../constants/urls';
import { updateNavigationState } from '../utils/navigationUtils';
const useAuth = () => {
const [user, setUser] = useRecoilState(userState);
const [navigation, setNavigation] = useRecoilState(navigationState);
const getUserFromStorage = async () => {
/*Check User Data In Storage */
const storedUserData = await AsyncStorage.getItem(AS_USER_KEY);
/* Set User State Only If User Data Found In Storage */
console.log("🌼 getUserFromStorage storedUserData",storedUserData);
// console.log(user);
if(storedUserData) {
const parsedUserData = JSON.parse(storedUserData);
setUser({ ...parsedUserData, loading: false });
console.log("πŸš€ ~ getUserFromStorage ~ parsedUserData:", parsedUserData)
updateNavigationState(parsedUserData, setNavigation);
} else {
setUser({...user, loading: false});
}
}
const loginUser = async userData => {
console.log('login userData',userData);
const userLoginData = {
...user,
session: userData,
userType: userData.user_type,
onboarding:
userData.user_type === 1
? !user.profile
? false
: true
: false,
profile: null,
locations: userData.locations || [],
err:null
};
setUser(userLoginData);
await AsyncStorage.setItem(AS_USER_KEY, JSON.stringify(userLoginData));
// replaceStack('PrivateStack');
updateNavigationState(userLoginData, setNavigation);
};
const logOutUser = async () => {
console.log("logOutUser");
if (user.userType === 1) {
defaultCoachDataState();
} else {
defaultPLayerDataState();
}
setUser(user.userType === 1 ? defaultCoachState : defaultPlayerState );
updateNavigationState(user.userType === 1 ? defaultCoachState : defaultPlayerState, setNavigation);
await AsyncStorage.setItem(AS_USER_KEY, JSON.stringify(user.userType === 1 ? defaultCoachState : defaultPlayerState));
// replaceStack('PublicStack');
}
}
// UserStateHandler.js
import React, { createRef, useImperativeHandle } from 'react';
import { useRecoilState } from 'recoil';
import { userState } from '../store/atoms';
// Create the ref
export const userGlobalRef = createRef();
const UserStateHandler = React.forwardRef((props, ref) => {
const [user, setUser] = useRecoilState(userState);
useImperativeHandle(ref, () => ({
setUser: (userData) => setUser(userData),
}));
return null; // This component does not need to render anything
});
// Export the component
export default UserStateHandler;
//decideswhere to redirect and restores session
import { useEffect } from 'react';
import { useNavigation } from '@react-navigation/native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { restoreSession } from '../utils/session';
import { updateNavigationState } from '../utils/navigationUtils';
import { replaceStack } from '../navigation/utils';
import useAuth from '../hooks/useAuth';
import { AS_USER_KEY } from '../constants/urls';
const useSession = () => {
const { setNavigation } = useAuth();
const gotoWelcomeScreen = () => {
console.log("gotoWelcomeScreen");
replaceStack('PublicStack');
};
const gotoAuthScreen = (sessionDetailsParam) => {
console.log("πŸ‘‰ gotoAuthScreen private stack authscreen", sessionDetailsParam);
updateNavigationState(sessionDetailsParam, setNavigation);
};
const initAppFlow = async () => {
try {
console.log("🌱 initAppFlow: checking for existing user session");
const sessionDetails = await restoreSession();
gotoAuthScreen(sessionDetails);
} catch (e) {
console.log("Session does not exist or cannot be restored", e);
await AsyncStorage.removeItem(AS_USER_KEY).catch(() => null);
gotoWelcomeScreen();
}
};
useEffect(() => {
initAppFlow();
}, []);
return null;
};
export default useSession;
import {
createNavigationContainerRef,
StackActions,
} from '@react-navigation/native';
export const navigationRef = createNavigationContainerRef();
export function goTo(name, params = {}) {
if (navigationRef.isReady()) {
navigationRef.navigate(name, params);
}
}
export function replaceStack(name, params = {}) {
if (navigationRef.isReady()) {
navigationRef.dispatch(StackActions.replace(name, params));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment