Fakultas Ilmu Komputer UI

Commit 5b4711f9 authored by Wulan Mantiri's avatar Wulan Mantiri
Browse files

Integrate payment integration

parent 7f321890
...@@ -23,6 +23,12 @@ ...@@ -23,6 +23,12 @@
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:host="mobile.dietela.id" android:scheme="http" />
</intent-filter>
</activity> </activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" /> <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application> </application>
......
import React from 'react'; import React from 'react';
import { render, fireEvent, waitFor } from 'utils/testing'; import { withAuthRender, fireEvent, waitFor } from 'utils/testing';
import * as ROUTES from 'constants/routes'; import * as ROUTES from 'constants/routes';
import LogoutButton from '.'; import LogoutButton from '.';
...@@ -30,11 +30,11 @@ describe('LogoutButton', () => { ...@@ -30,11 +30,11 @@ describe('LogoutButton', () => {
}; };
it('renders correctly', () => { it('renders correctly', () => {
render(<LogoutButton />, ROUTES.checkout); withAuthRender(<LogoutButton />, ROUTES.checkout);
}); });
it('calls logout and redirects to initial page when clicked', async () => { it('calls logout and redirects to initial page when clicked', async () => {
const { getByTestId } = render( const { getByTestId } = withAuthRender(
<UserContext.Provider value={userContextMock}> <UserContext.Provider value={userContextMock}>
<LogoutButton /> <LogoutButton />
</UserContext.Provider>, </UserContext.Provider>,
......
...@@ -2,26 +2,17 @@ import React, { FC, useContext } from 'react'; ...@@ -2,26 +2,17 @@ import React, { FC, useContext } from 'react';
import { UserContext } from 'provider'; import { UserContext } from 'provider';
import { Button, Icon } from 'react-native-elements'; import { Button, Icon } from 'react-native-elements';
import { StyleSheet, View } from 'react-native'; import { StyleSheet, View } from 'react-native';
import { useNavigation } from '@react-navigation/core';
import * as ROUTES from 'constants/routes';
const LogoutButton: FC = () => { const LogoutButton: FC<{
tintColor?: string | undefined;
}> = () => {
const { logout, isAuthenticated } = useContext(UserContext); const { logout, isAuthenticated } = useContext(UserContext);
const navigation = useNavigation();
const handlePress = async () => {
await logout();
navigation.reset({
index: 0,
routes: [{ name: ROUTES.initial }],
});
};
return isAuthenticated ? ( return isAuthenticated ? (
<Button <Button
icon={<Icon name="logout" type="material" />} icon={<Icon name="logout" type="material" />}
buttonStyle={styles.button} buttonStyle={styles.button}
onPress={handlePress} onPress={logout}
testID="logoutButton" testID="logoutButton"
/> />
) : ( ) : (
......
import React, { FC } from 'react'; import React, { FC, useContext } from 'react';
import { NavigationContainer } from '@react-navigation/native'; import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack'; import { createStackNavigator } from '@react-navigation/stack';
import { ThemeProvider } from 'react-native-elements'; import { ThemeProvider } from 'react-native-elements';
import Toast from 'react-native-toast-message'; import Toast from 'react-native-toast-message';
import * as ROUTES from 'constants/routes'; import ContextProvider, { UserContext } from 'provider';
import { navigation } from 'constants/navigation';
import ContextProvider from 'provider';
import { theme } from 'styles/theme'; import { theme } from 'styles/theme';
import { screenOptions, toastConfig } from './styles'; import { screenOptions, toastConfig } from './styles';
import LogoutButton from './LogoutButton'; import LogoutButton from './LogoutButton';
import { getNavigation } from './schema';
const Stack = createStackNavigator(); const Stack = createStackNavigator();
const NavigationStack: FC = () => {
const { isAuthenticated, isUnpaidClient } = useContext(UserContext);
const { initialRoute, navigation } = getNavigation(
isAuthenticated,
isUnpaidClient,
);
return (
<NavigationContainer>
<Stack.Navigator
initialRouteName={initialRoute}
screenOptions={screenOptions}>
<>
{navigation.map((nav, i) => (
<Stack.Screen
key={`nav${i}`}
name={nav.name}
component={nav.component}
options={{
title: nav.header,
headerShown: Boolean(nav.header),
headerRight: LogoutButton,
}}
/>
))}
</>
</Stack.Navigator>
</NavigationContainer>
);
};
const App: FC = () => { const App: FC = () => {
return ( return (
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<ContextProvider> <ContextProvider>
<NavigationContainer> <NavigationStack />
<Stack.Navigator
initialRouteName={ROUTES.initial}
screenOptions={screenOptions}>
{navigation.map((nav, i) => (
<Stack.Screen
key={`nav${i}`}
name={nav.name}
component={nav.component}
options={{
title: nav.header,
headerShown: Boolean(nav.header),
headerRight: LogoutButton,
}}
/>
))}
</Stack.Navigator>
</NavigationContainer>
<Toast config={toastConfig} ref={Toast.setRef} /> <Toast config={toastConfig} ref={Toast.setRef} />
</ContextProvider> </ContextProvider>
</ThemeProvider> </ThemeProvider>
......
import * as ROUTES from 'constants/routes';
import {
publicNavigation,
privateNavigation,
unpaidClientNavigation,
} from 'constants/navigation';
export const getNavigation = (
isAuthenticated: boolean,
isUnpaidClient: boolean,
) => {
if (isAuthenticated) {
return isUnpaidClient
? {
initialRoute: ROUTES.checkout,
navigation: unpaidClientNavigation,
}
: {
initialRoute: ROUTES.profile,
navigation: privateNavigation,
};
}
return {
initialRoute: ROUTES.initial,
navigation: publicNavigation,
};
};
...@@ -10,6 +10,7 @@ import { ...@@ -10,6 +10,7 @@ import {
LoginPage, LoginPage,
ProgramDetail, ProgramDetail,
NutritionistDetail, NutritionistDetail,
PaymentResult,
} from 'scenes'; } from 'scenes';
import { FC } from 'react'; import { FC } from 'react';
...@@ -19,11 +20,7 @@ export interface NavRoute { ...@@ -19,11 +20,7 @@ export interface NavRoute {
header?: string; header?: string;
} }
export const navigation: NavRoute[] = [ const navigation: NavRoute[] = [
{
name: ROUTES.initial,
component: InitialPage,
},
{ {
name: ROUTES.allAccessQuestionnaire, name: ROUTES.allAccessQuestionnaire,
component: AllAccessQuestionnaire, component: AllAccessQuestionnaire,
...@@ -39,11 +36,6 @@ export const navigation: NavRoute[] = [ ...@@ -39,11 +36,6 @@ export const navigation: NavRoute[] = [
component: ChoosePlan, component: ChoosePlan,
header: 'Choose Plan', header: 'Choose Plan',
}, },
{
name: ROUTES.checkout,
component: Checkout,
header: 'Checkout',
},
{ {
name: ROUTES.programDetail, name: ROUTES.programDetail,
component: ProgramDetail, component: ProgramDetail,
...@@ -54,6 +46,14 @@ export const navigation: NavRoute[] = [ ...@@ -54,6 +46,14 @@ export const navigation: NavRoute[] = [
component: NutritionistDetail, component: NutritionistDetail,
header: 'Nutrisionis', header: 'Nutrisionis',
}, },
];
export const publicNavigation: NavRoute[] = [
{
name: ROUTES.initial,
component: InitialPage,
},
...navigation,
{ {
name: ROUTES.registration, name: ROUTES.registration,
component: ManualRegistrationPage, component: ManualRegistrationPage,
...@@ -64,6 +64,23 @@ export const navigation: NavRoute[] = [ ...@@ -64,6 +64,23 @@ export const navigation: NavRoute[] = [
component: LoginPage, component: LoginPage,
header: 'Login', header: 'Login',
}, },
];
export const unpaidClientNavigation: NavRoute[] = [
{
name: ROUTES.checkout,
component: Checkout,
header: 'Checkout',
},
{
name: ROUTES.paymentResult,
component: PaymentResult,
header: 'Pembayaran',
},
...navigation,
];
export const privateNavigation: NavRoute[] = [
{ {
name: ROUTES.profile, name: ROUTES.profile,
component: ComingSoonPage, component: ComingSoonPage,
......
...@@ -14,3 +14,6 @@ export const registration = 'registration'; ...@@ -14,3 +14,6 @@ export const registration = 'registration';
export const login = 'login'; export const login = 'login';
export const profile = 'profile'; export const profile = 'profile';
const payment = 'payment';
export const paymentResult = `${payment}/result`;
export { default as useApi } from './useApi'; export { default as useApi } from './useApi';
export { default as useAuthEffect } from './useAuthEffect'; export { default as useAuthEffect } from './useAuthEffect';
export { default as useAuthGuardEffect } from './useAuthGuardEffect';
export { default as useForm } from './useForm'; export { default as useForm } from './useForm';
export { default as useLinkingEffect } from './useLinkingEffect';
...@@ -5,10 +5,8 @@ import * as ROUTES from 'constants/routes'; ...@@ -5,10 +5,8 @@ import * as ROUTES from 'constants/routes';
import CACHE_KEYS from 'constants/cacheKeys'; import CACHE_KEYS from 'constants/cacheKeys';
import { getCache } from 'utils/cache'; import { getCache } from 'utils/cache';
const useAuthEffect = (isLogin?: boolean) => { const useAuthEffect = () => {
const { isAuthenticated, isUnpaidClient, isFirstLoading } = useContext( const { isFirstLoading } = useContext(UserContext);
UserContext,
);
const navigation = useNavigation(); const navigation = useNavigation();
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
...@@ -34,26 +32,8 @@ const useAuthEffect = (isLogin?: boolean) => { ...@@ -34,26 +32,8 @@ const useAuthEffect = (isLogin?: boolean) => {
}, [navigation]); }, [navigation]);
useEffect(() => { useEffect(() => {
if (isAuthenticated) { checkCart();
if (isUnpaidClient) { }, [checkCart]);
navigation.reset({
index: 0,
routes: [{ name: ROUTES.checkout }],
});
} else {
navigation.reset({
index: 0,
routes: [{ name: ROUTES.profile }],
});
}
} else if (isLogin) {
checkCart();
}
return () => {
setIsLoading(false);
};
}, [checkCart, isLogin, isAuthenticated, isUnpaidClient, navigation]);
return isFirstLoading || isLoading; return isFirstLoading || isLoading;
}; };
......
import { useContext, useEffect } from 'react';
import { useNavigation } from '@react-navigation/native';
import { UserContext } from 'provider';
import * as ROUTES from 'constants/routes';
const useAuthGuardEffect = (signupFallback?: boolean) => {
const { isAuthenticated, firstAuthenticated } = useContext(UserContext);
const navigation = useNavigation();
useEffect(() => {
if (!isAuthenticated && firstAuthenticated) {
navigation.navigate(signupFallback ? ROUTES.registration : ROUTES.login);
}
}, [isAuthenticated, signupFallback, navigation, firstAuthenticated]);
};
export default useAuthGuardEffect;
import { useEffect } from 'react';
import { Linking } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import * as ROUTES from 'constants/routes';
const useLinkingEffect = () => {
const navigation = useNavigation();
const getQueryParams = (path: string | null) => {
return path
? JSON.parse(
'{"' +
decodeURI(path.replace(/&/g, '","').replace(/[=]/g, '":"')) +
'"}',
)
: {};
};
useEffect(() => {
const navigate = (url: string | null) => {
if (url) {
const splitUrl = url.split('?');
const path = splitUrl[0].split('/').slice(3).join('/');
const queryParams = getQueryParams(splitUrl[1]);
if (path === 'payment') {
navigation.reset({
index: 0,
routes: [{ name: ROUTES.paymentResult, params: queryParams }],
});
}
}
};
const handleOpenURL = (event: { url: string }) => {
navigate(event.url);
};
Linking.addEventListener('url', handleOpenURL);
return () => {
Linking.removeEventListener('url', handleOpenURL);
};
}, [navigation]);
};
export default useLinkingEffect;
...@@ -22,6 +22,7 @@ import { ...@@ -22,6 +22,7 @@ import {
import { set401Callback, setAuthHeader, resetAuthHeader } from 'services/api'; import { set401Callback, setAuthHeader, resetAuthHeader } from 'services/api';
import { iUserContext } from './types'; import { iUserContext } from './types';
import { TransactionStatus } from 'services/payment/models';
const initialUser = { const initialUser = {
id: null, id: null,
...@@ -32,7 +33,6 @@ const initialUser = { ...@@ -32,7 +33,6 @@ const initialUser = {
export const UserContext = createContext<iUserContext>({ export const UserContext = createContext<iUserContext>({
user: initialUser, user: initialUser,
firstAuthenticated: true,
isAuthenticated: false, isAuthenticated: false,
isUnpaidClient: false, isUnpaidClient: false,
isPaidClient: false, isPaidClient: false,
...@@ -50,8 +50,9 @@ export const useUserContext = (): iUserContext => { ...@@ -50,8 +50,9 @@ export const useUserContext = (): iUserContext => {
const [user, setUser] = useState<User>(initialUser); const [user, setUser] = useState<User>(initialUser);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [isFirstLoading, setIsFirstLoading] = useState(false); const [isFirstLoading, setIsFirstLoading] = useState(false);
const [firstAuth, setFirstAuth] = useState(true); const [transactionStatus, setTransactionStatus] = useState(
const [clientHasPaid] = useState(false); TransactionStatus.UNPAID,
);
const logout = useCallback(async () => { const logout = useCallback(async () => {
await GoogleSignin.signOut(); await GoogleSignin.signOut();
...@@ -59,7 +60,6 @@ export const useUserContext = (): iUserContext => { ...@@ -59,7 +60,6 @@ export const useUserContext = (): iUserContext => {
await removeCache(CACHE_KEYS.refreshToken); await removeCache(CACHE_KEYS.refreshToken);
setUser(initialUser); setUser(initialUser);
resetAuthHeader(); resetAuthHeader();
setFirstAuth(false);
}, []); }, []);
const getUser = useCallback(async () => { const getUser = useCallback(async () => {
...@@ -69,7 +69,9 @@ export const useUserContext = (): iUserContext => { ...@@ -69,7 +69,9 @@ export const useUserContext = (): iUserContext => {
setAuthHeader(token); setAuthHeader(token);
const response = await retrieveUserApi(); const response = await retrieveUserApi();
if (response.success && response.data) { if (response.success && response.data) {
setUser(response.data); const { cart, ...userData } = response.data;
setUser(userData);
setTransactionStatus(cart.transaction_status);
} else { } else {
await logout(); await logout();
Toast.show({ Toast.show({
...@@ -167,10 +169,11 @@ export const useUserContext = (): iUserContext => { ...@@ -167,10 +169,11 @@ export const useUserContext = (): iUserContext => {
return { return {
user, user,
firstAuthenticated: firstAuth,
isAuthenticated: user.id !== null, isAuthenticated: user.id !== null,
isUnpaidClient: user.role === UserRole.CLIENT, isUnpaidClient:
isPaidClient: user.role === UserRole.CLIENT && clientHasPaid, user.role === UserRole.CLIENT &&
transactionStatus === TransactionStatus.UNPAID,
isPaidClient: user.role === UserRole.CLIENT,
isNutritionist: user.role === UserRole.NUTRITIONIST, isNutritionist: user.role === UserRole.NUTRITIONIST,
isAdmin: user.role === UserRole.ADMIN, isAdmin: user.role === UserRole.ADMIN,
isLoading, isLoading,
......
...@@ -9,7 +9,6 @@ import { ...@@ -9,7 +9,6 @@ import {
export interface iUserContext { export interface iUserContext {
user: User; user: User;
firstAuthenticated: boolean;
isAuthenticated: boolean; isAuthenticated: boolean;
isUnpaidClient: boolean; isUnpaidClient: boolean;
isPaidClient: boolean; isPaidClient: boolean;
......
...@@ -45,7 +45,7 @@ const Login: FC = () => { ...@@ -45,7 +45,7 @@ const Login: FC = () => {
}, },
}); });
const isProcessing = useAuthEffect(true); const isProcessing = useAuthEffect();
if (isProcessing) { if (isProcessing) {
return <Loader />; return <Loader />;
......
import React, { FC, useContext } from 'react'; import React, { FC, useContext } from 'react';
import { useAuthEffect, useForm } from 'hooks'; import { useForm } from 'hooks';
import { ScrollView } from 'react-native-gesture-handler'; import { ScrollView } from 'react-native-gesture-handler';
import { useNavigation } from '@react-navigation/core'; import { useNavigation } from '@react-navigation/core';
...@@ -50,8 +50,6 @@ const ManualRegistrationPage: FC = () => { ...@@ -50,8 +50,6 @@ const ManualRegistrationPage: FC = () => {
const signupWithGoogle = () => loginWithGoogle(false); const signupWithGoogle = () => loginWithGoogle(false);
useAuthEffect();
return ( return (
<ScrollView contentContainerStyle={layoutStyles}> <ScrollView contentContainerStyle={layoutStyles}>
{textField.map((fieldProps, i) => ( {textField.map((fieldProps, i) => (
......
import React, { FC, useCallback } from 'react'; import React, { FC, useCallback, useState } from 'react';
import { View } from 'react-native'; import { View, Linking } from 'react-native';
import { Text, Button } from 'react-native-elements'; import { Text, Button } from 'react-native-elements';
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';