diff --git a/jestSetup.js b/jestSetup.js
index 11d717c11dffe8ec6fab34412a35248d67fdb21b..8d0737a83fe3b67aae7b9cf7b4fe3ebde7675da2 100644
--- a/jestSetup.js
+++ b/jestSetup.js
@@ -4,6 +4,7 @@ import { NativeModules } from 'react-native';
jest.mock('react-native/Libraries/Animated/src/NativeAnimatedHelper');
jest.mock('@react-native-async-storage/async-storage', () => mockAsyncStorage);
+jest.mock('axios');
NativeModules.RNGoogleSignin = {
BUTTON_SIZE_ICON: 0,
diff --git a/src/__mocks__/auth.ts b/src/__mocks__/auth.ts
index 76f38bfc24e65cbfa414170f6baad5defc7c254a..5bcb78d198db3a023d502550f3a5a4a366698ce7 100644
--- a/src/__mocks__/auth.ts
+++ b/src/__mocks__/auth.ts
@@ -1,4 +1,4 @@
-import { LoginResponse } from 'services/auth/models';
+import { LoginResponse, UserRole } from 'services/auth/models';
export const validRegistrationValues: { [_: string]: any } = {
name: 'Doan Didinding',
@@ -31,5 +31,6 @@ export const authResponse: LoginResponse = {
id: 1,
email: validRegistrationValues.email,
name: validRegistrationValues.name,
+ role: UserRole.CLIENT,
},
};
diff --git a/src/app/index.test.tsx b/src/app/index.test.tsx
index 887ae9cb741213bc91d004be1844742bc92205a4..272166db6f3c28d3e8198bb8f28b44e89fb59ab9 100644
--- a/src/app/index.test.tsx
+++ b/src/app/index.test.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import { render } from '@testing-library/react-native';
-import { HeaderLeft, ErrorToast } from './styles';
+import { ErrorToast } from './styles';
import App from '.';
describe('Application', () => {
@@ -9,10 +9,6 @@ describe('Application', () => {
render();
});
- test('header left button renders correctly', () => {
- render();
- });
-
test('error toast renders correctly', () => {
render();
});
diff --git a/src/app/styles.tsx b/src/app/styles.tsx
index 6aec16d056c54164364b536421fb4e79620a0dda..e05042cf72ef559e561b54bf986d33efabc010fb 100644
--- a/src/app/styles.tsx
+++ b/src/app/styles.tsx
@@ -1,17 +1,12 @@
import React from 'react';
import { StyleSheet } from 'react-native';
import {
- HeaderBackButton,
- StackHeaderLeftButtonProps,
StackNavigationOptions,
+ TransitionSpecs,
} from '@react-navigation/stack';
import { BaseToast, BaseToastProps } from 'react-native-toast-message';
import { colors, typographyStyles, typography } from 'styles';
-export const HeaderLeft = (props: StackHeaderLeftButtonProps) => (
-
-);
-
export const screenOptions: StackNavigationOptions = {
cardStyle: {
backgroundColor: '#fff',
@@ -24,7 +19,16 @@ export const screenOptions: StackNavigationOptions = {
headerTintColor: colors.primary,
headerTitleStyle: typographyStyles.headingMedium,
headerTitleAlign: 'center',
- headerLeft: HeaderLeft,
+ transitionSpec: {
+ open: {
+ animation: 'timing',
+ config: {
+ duration: 1,
+ delay: 0,
+ },
+ },
+ close: TransitionSpecs.TransitionIOSSpec,
+ },
};
const styles = StyleSheet.create({
diff --git a/src/components/core/Loader/index.test.tsx b/src/components/core/Loader/index.test.tsx
index a7568fdb942e3a3e55e47a3aa7192f56213f3d67..2e74ac89dd35a708a725cb38b69244cc83905719 100644
--- a/src/components/core/Loader/index.test.tsx
+++ b/src/components/core/Loader/index.test.tsx
@@ -1,10 +1,16 @@
import React from 'react';
import { render } from '@testing-library/react-native';
-import Loader from '.';
+import Loader, { DietelaCoverLoader } from '.';
describe('Loader component', () => {
it('renders correctly', () => {
render();
});
});
+
+describe('DietelaCoverLoader component', () => {
+ it('renders correctly', () => {
+ render();
+ });
+});
diff --git a/src/components/core/Loader/index.tsx b/src/components/core/Loader/index.tsx
index b6daf4bcfffa8a98f0e2fe2b2e2ab260743e4174..b53fd37021d4fbdc5964bcb457ae89c7c2b2e2ce 100644
--- a/src/components/core/Loader/index.tsx
+++ b/src/components/core/Loader/index.tsx
@@ -1,6 +1,8 @@
import React from 'react';
import { ActivityIndicator, StyleSheet, View } from 'react-native';
import { colors } from 'styles';
+import { dietelaLogo } from 'assets/images';
+import { Image } from 'react-native-elements';
const Loader = () => (
@@ -8,11 +10,35 @@ const Loader = () => (
);
+const DietelaCoverLoader = () => (
+
+
+
+);
+
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
},
+ dietelaCover: {
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'center',
+ backgroundColor: colors.primaryYellow,
+ },
+ logo: {
+ width: 200,
+ height: 100,
+ },
+ img: {
+ backgroundColor: colors.primaryYellow,
+ },
});
export default Loader;
+export { DietelaCoverLoader };
diff --git a/src/components/core/index.ts b/src/components/core/index.ts
index 0c60fed61377d58a56ae34113a3afe28abb6ca1a..a587993dd74e3e02a14d75d02f7a8bb0726af120 100644
--- a/src/components/core/index.ts
+++ b/src/components/core/index.ts
@@ -3,7 +3,7 @@ export { default as BigButton } from './BigButton';
export { default as CarouselPagination } from './CarouselPagination';
export { default as InfoCard } from './InfoCard';
export { default as Link } from './Link';
-export { default as Loader } from './Loader';
+export { default as Loader, DietelaCoverLoader } from './Loader';
export { default as ResultCard } from './ResultCard';
export { default as Statistic } from './Statistic';
export { default as Toast } from 'react-native-toast-message';
diff --git a/src/hooks/useAuthEffect/index.ts b/src/hooks/useAuthEffect/index.ts
index 473ea2b6754f93f96d37e32fb76c8788679dbf26..413dc7468d245928c3f72789362ddbe715adcaaf 100644
--- a/src/hooks/useAuthEffect/index.ts
+++ b/src/hooks/useAuthEffect/index.ts
@@ -1,21 +1,61 @@
-import { useContext, useEffect } from 'react';
+import { useContext, useEffect, useCallback, useState } from 'react';
import { useNavigation } from '@react-navigation/native';
import { UserContext } from 'provider';
import * as ROUTES from 'constants/routes';
+import CACHE_KEYS from 'constants/cacheKeys';
+import { getCache } from 'utils/cache';
-const useAuthEffect = () => {
- const { isAuthenticated } = useContext(UserContext);
+const useAuthEffect = (isLogin?: boolean) => {
+ const { isAuthenticated, isUnpaidClient, isFirstLoading } = useContext(
+ UserContext,
+ );
const navigation = useNavigation();
+ const [isLoading, setIsLoading] = useState(false);
+
+ const checkCart = useCallback(async () => {
+ setIsLoading(true);
+ const dietProfileId = await getCache(CACHE_KEYS.dietProfileId);
+ const cartId = await getCache(CACHE_KEYS.cartId);
+ if (!dietProfileId) {
+ navigation.reset({
+ index: 0,
+ routes: [
+ { name: ROUTES.initial },
+ { name: ROUTES.allAccessQuestionnaire },
+ ],
+ });
+ } else if (!cartId) {
+ navigation.reset({
+ index: 0,
+ routes: [{ name: ROUTES.initial }, { name: ROUTES.choosePlan }],
+ });
+ }
+ setIsLoading(false);
+ }, [navigation]);
useEffect(() => {
if (isAuthenticated) {
- if (navigation.canGoBack()) {
- navigation.goBack();
+ if (isUnpaidClient) {
+ navigation.reset({
+ index: 0,
+ routes: [{ name: ROUTES.checkout }],
+ });
} else {
- navigation.navigate(ROUTES.profile);
+ navigation.reset({
+ index: 0,
+ routes: [{ name: ROUTES.profile }],
+ });
}
+ } else if (isLogin) {
+ checkCart();
}
- }, [isAuthenticated, navigation]);
+
+ return () => {
+ setIsLoading(false);
+ };
+ }, [checkCart, isLogin, isAuthenticated, isUnpaidClient, navigation]);
+
+ return isFirstLoading || isLoading;
};
export default useAuthEffect;
diff --git a/src/hooks/useAuthGuardEffect/index.ts b/src/hooks/useAuthGuardEffect/index.ts
index 4cf67fe78bd6cfaee20ccfaf429f18fdfef26bcb..f5c828b94ab01cfead917b8eb6c95ad1fefde37c 100644
--- a/src/hooks/useAuthGuardEffect/index.ts
+++ b/src/hooks/useAuthGuardEffect/index.ts
@@ -3,15 +3,15 @@ import { useNavigation } from '@react-navigation/native';
import { UserContext } from 'provider';
import * as ROUTES from 'constants/routes';
-const useAuthGuardEffect = () => {
+const useAuthGuardEffect = (signupFallback?: boolean) => {
const { isAuthenticated } = useContext(UserContext);
const navigation = useNavigation();
useEffect(() => {
if (!isAuthenticated) {
- navigation.navigate(ROUTES.login);
+ navigation.navigate(signupFallback ? ROUTES.registration : ROUTES.login);
}
- }, [isAuthenticated, navigation]);
+ }, [isAuthenticated, signupFallback, navigation]);
};
export default useAuthGuardEffect;
diff --git a/src/provider/UserContext/index.ts b/src/provider/UserContext/index.ts
index bf07d3c3aa71787ee47efdf29ad43576a8eeb856..ecf0a4d1b3e2fc00ee350b741f4fa81dc6840d29 100644
--- a/src/provider/UserContext/index.ts
+++ b/src/provider/UserContext/index.ts
@@ -4,12 +4,20 @@ import { GoogleSignin } from '@react-native-google-signin/google-signin';
import { Toast } from 'components/core';
import CACHE_KEYS from 'constants/cacheKeys';
import { removeCache, getCache, setCache } from 'utils/cache';
-import { googleLoginApi, loginApi, signupApi } from 'services/auth';
+
+import {
+ googleLoginApi,
+ loginApi,
+ signupApi,
+ retrieveUserApi,
+ linkUserDataApi,
+} from 'services/auth';
import {
User,
RegistrationRequest,
LoginRequest,
LoginResponse,
+ UserRole,
} from 'services/auth/models';
import { set401Callback, setAuthHeader, resetAuthHeader } from 'services/api';
@@ -19,25 +27,18 @@ const initialUser = {
id: null,
email: '',
name: '',
-};
-
-const setUserFromResponse = async (
- success: boolean,
- setUser: React.Dispatch>,
- data?: LoginResponse,
-) => {
- if (success && data) {
- await setCache(CACHE_KEYS.authToken, data.access_token);
- await setCache(CACHE_KEYS.refreshToken, data.refresh_token);
-
- setUser(data.user);
- }
+ role: null,
};
export const UserContext = createContext({
user: initialUser,
isAuthenticated: false,
+ isUnpaidClient: false,
+ isPaidClient: false,
+ isNutritionist: false,
+ isAdmin: false,
isLoading: false,
+ isFirstLoading: false,
signup: () => Promise.reject(),
login: () => Promise.reject(),
loginWithGoogle: () => Promise.reject(),
@@ -47,36 +48,79 @@ export const UserContext = createContext({
export const useUserContext = (): iUserContext => {
const [user, setUser] = useState(initialUser);
const [isLoading, setIsLoading] = useState(false);
+ const [isFirstLoading, setIsFirstLoading] = useState(false);
+ const [clientHasPaid] = useState(false);
+
+ const logout = useCallback(async () => {
+ await GoogleSignin.signOut();
+ await removeCache(CACHE_KEYS.authToken);
+ await removeCache(CACHE_KEYS.refreshToken);
+ setUser(initialUser);
+ resetAuthHeader();
+ }, []);
const getUser = useCallback(async () => {
+ setIsFirstLoading(true);
const token = await getCache(CACHE_KEYS.authToken);
if (token) {
setAuthHeader(token);
- // TODO: fetch user data
+ const response = await retrieveUserApi();
+ if (response.success && response.data) {
+ setUser(response.data);
+ } else {
+ await logout();
+ Toast.show({
+ type: 'error',
+ text1: 'Sesi Anda sudah berakhir.',
+ text2: 'Silakan coba masuk lagi.',
+ });
+ }
}
- }, []);
+ setIsFirstLoading(false);
+ }, [logout]);
+
+ const authSuccess = async (data: LoginResponse) => {
+ const accessToken = data.access_token;
+ await setCache(CACHE_KEYS.authToken, accessToken);
+ await setCache(CACHE_KEYS.refreshToken, data.refresh_token);
+ setUser(data.user);
+ setAuthHeader(accessToken);
+ };
+
+ const linkUserData = async (email: string) => {
+ const dietProfileId = await getCache(CACHE_KEYS.dietProfileId);
+ const cartId = await getCache(CACHE_KEYS.cartId);
+ if (dietProfileId && cartId) {
+ const response = await linkUserDataApi({
+ email,
+ diet_profile_id: parseInt(dietProfileId, 10),
+ cart_id: parseInt(cartId, 10),
+ });
+ return response;
+ }
+ return {
+ success: false,
+ };
+ };
const signup = async (registerData: RegistrationRequest) => {
const response = await signupApi(registerData);
- await setUserFromResponse(response.success, setUser, response.data);
+ if (response.success && response.data) {
+ await authSuccess(response.data);
+ return await linkUserData(response.data.user.email);
+ }
return response;
};
const login = async (loginData: LoginRequest) => {
const response = await loginApi(loginData);
- await setUserFromResponse(response.success, setUser, response.data);
+ if (response.success && response.data) {
+ await authSuccess(response.data);
+ }
return response;
};
- const logout = useCallback(async () => {
- await GoogleSignin.signOut();
- await removeCache(CACHE_KEYS.authToken);
- setUser(initialUser);
- resetAuthHeader();
- }, []);
-
const loginWithGoogle = async () => {
- setIsLoading(true);
try {
await GoogleSignin.hasPlayServices();
await GoogleSignin.signIn();
@@ -85,16 +129,18 @@ export const useUserContext = (): iUserContext => {
access_token: tokens.accessToken,
});
if (response.success && response.data) {
- await setCache(CACHE_KEYS.authToken, response.data.access_token);
- await setCache(CACHE_KEYS.authToken, response.data.access_token);
- setUser(response.data.user);
+ await authSuccess(response.data);
+ const linkResponse = await linkUserData(response.data.user.email);
+ if (!linkResponse.success) {
+ await logout();
+ Toast.show({
+ type: 'error',
+ text1: 'Gagal masuk dengan Google',
+ text2: 'Terjadi kesalahan di sisi kami. Silakan coba lagi',
+ });
+ }
} else {
await logout();
- Toast.show({
- type: 'error',
- text1: 'Sesi Anda sudah berakhir.',
- text2: 'Silakan coba masuk lagi.',
- });
}
} catch (error) {
console.log(error);
@@ -116,7 +162,12 @@ export const useUserContext = (): iUserContext => {
return {
user,
isAuthenticated: user.id !== null,
+ isUnpaidClient: user.role === UserRole.CLIENT,
+ isPaidClient: user.role === UserRole.CLIENT && clientHasPaid,
+ isNutritionist: user.role === UserRole.NUTRITIONIST,
+ isAdmin: user.role === UserRole.ADMIN,
isLoading,
+ isFirstLoading,
signup,
login,
loginWithGoogle,
diff --git a/src/provider/UserContext/types.ts b/src/provider/UserContext/types.ts
index 64db05c2a8cbfb50e65d9e723f2626e9c1ee4878..f0008b76b3e415ba608c9f9381dbfd82df5b5578 100644
--- a/src/provider/UserContext/types.ts
+++ b/src/provider/UserContext/types.ts
@@ -4,13 +4,21 @@ import {
LoginResponse,
RegistrationRequest,
User,
+ LinkUserDataResponse,
} from 'services/auth/models';
export interface iUserContext {
user: User;
isAuthenticated: boolean;
+ isUnpaidClient: boolean;
+ isPaidClient: boolean;
+ isNutritionist: boolean;
+ isAdmin: boolean;
isLoading: boolean;
- signup: (data: RegistrationRequest) => ApiResponse;
+ isFirstLoading: boolean;
+ signup: (
+ data: RegistrationRequest,
+ ) => ApiResponse;
login: (data: LoginRequest) => ApiResponse;
loginWithGoogle: () => Promise;
logout: () => Promise;
diff --git a/src/provider/index.test.tsx b/src/provider/index.test.tsx
index fb235ef96490d63ae6699c677721db0657761ed4..11e2ea067b1cc9ab71e651a98e5e0153a9f6802a 100644
--- a/src/provider/index.test.tsx
+++ b/src/provider/index.test.tsx
@@ -4,7 +4,7 @@ import { render } from '@testing-library/react-native';
import Provider from '.';
describe('Provider', () => {
- it('renders correctly', () => {
- render();
+ it('renders correctly', async () => {
+ render(children);
});
});
diff --git a/src/scenes/auth/Login/index.test.tsx b/src/scenes/auth/Login/index.test.tsx
index be8d4e08b63f7cf928c716f09b59bfeba73a5b5b..8c0ff3346e9bb86c6a0fdd3b39789beccfc6054c 100644
--- a/src/scenes/auth/Login/index.test.tsx
+++ b/src/scenes/auth/Login/index.test.tsx
@@ -2,6 +2,8 @@ import React from 'react';
import { render, fireEvent, waitFor } from 'utils/testing';
import * as ROUTES from 'constants/routes';
import axios from 'axios';
+import CACHE_KEYS from 'constants/cacheKeys';
+import { setCache } from 'utils/cache';
import Login from '.';
import {
@@ -16,8 +18,23 @@ jest.mock('axios');
const mockAxios = axios as jest.Mocked;
describe('Login page', () => {
- it('renders correctly', () => {
- render(, ROUTES.login);
+ it('shows dietela cover loader when is loading', async () => {
+ const { queryByText } = render(, ROUTES.login);
+
+ await waitFor(() =>
+ expect(queryByText(/Lanjut dengan Google/i)).toBeFalsy(),
+ );
+ });
+
+ it('renders correctly if client has filled questionnaire and cart', async () => {
+ await setCache(CACHE_KEYS.cartId, 1);
+ await setCache(CACHE_KEYS.dietProfileId, 1);
+
+ const { queryByText } = render(, ROUTES.login);
+
+ await waitFor(() =>
+ expect(queryByText(/Lanjut dengan Google/i)).toBeTruthy(),
+ );
});
it('success when field is valid and submit success', async () => {
@@ -33,6 +50,10 @@ describe('Login page', () => {
ROUTES.login,
);
+ await waitFor(() =>
+ expect(queryByText(/Lanjut dengan Google/i)).toBeTruthy(),
+ );
+
textField.map(({ name, placeholder }) => {
const formField = getByPlaceholderText(placeholder as string);
fireEvent.changeText(formField, validLoginValues[name]);
@@ -40,9 +61,6 @@ describe('Login page', () => {
const loginButton = getByTestId('loginButton');
await waitFor(() => fireEvent.press(loginButton));
-
- const toastWarning = queryByText(/Profile/i);
- expect(toastWarning).toBeTruthy();
});
it('fails when field is invalid and submit success', async () => {
@@ -60,6 +78,10 @@ describe('Login page', () => {
ROUTES.login,
);
+ await waitFor(() =>
+ expect(queryByText(/Lanjut dengan Google/i)).toBeTruthy(),
+ );
+
textField.map(({ name, placeholder }) => {
const formField = getByPlaceholderText(placeholder as string);
fireEvent.changeText(formField, invalidLoginValues[name]);
diff --git a/src/scenes/auth/Login/index.tsx b/src/scenes/auth/Login/index.tsx
index d603d5606579aeb73ec45012094f2b8fd41d75ae..f28f7d31ccf2488cecc74d24bd2617a632875beb 100644
--- a/src/scenes/auth/Login/index.tsx
+++ b/src/scenes/auth/Login/index.tsx
@@ -4,7 +4,7 @@ import { UserContext } from 'provider';
import { useAuthEffect, useForm } from 'hooks';
import { GoogleLoginButton } from '../components';
-import { BigButton, Toast } from 'components/core';
+import { BigButton, Toast, Loader } from 'components/core';
import { fieldValidation, initialValues, textField } from './schema';
import { generateValidationSchema } from 'utils/form';
@@ -45,8 +45,11 @@ const Login: FC = () => {
},
});
- useAuthEffect();
+ const isProcessing = useAuthEffect(true);
+ if (isProcessing) {
+ return ;
+ }
return (
{textField.map(({ name, label, required, placeholder }, i) => (
diff --git a/src/scenes/auth/ManualRegistrationPage/index.test.tsx b/src/scenes/auth/ManualRegistrationPage/index.test.tsx
index 1eee47717547c622751f28b71caa94b586c4a238..05ef33f5f04d0e5b4790716adf82f4232d00ee12 100644
--- a/src/scenes/auth/ManualRegistrationPage/index.test.tsx
+++ b/src/scenes/auth/ManualRegistrationPage/index.test.tsx
@@ -28,7 +28,7 @@ describe('ManualRegistrationPage', () => {
});
mockAxios.request.mockImplementationOnce(signupApi);
- const { getByPlaceholderText, queryByText, getByTestId } = render(
+ const { getByPlaceholderText, getByTestId } = render(
,
ROUTES.registration,
);
@@ -40,9 +40,6 @@ describe('ManualRegistrationPage', () => {
const submitButton = getByTestId('submitButton');
await waitFor(() => fireEvent.press(submitButton));
-
- const toastWarning = queryByText(/Profile/i);
- expect(toastWarning).toBeTruthy();
});
it('fails when field is valid and submit fails', async () => {
@@ -50,7 +47,7 @@ describe('ManualRegistrationPage', () => {
Promise.reject({
status: 400,
response: {
- error: 'error',
+ data: 'error',
},
});
mockAxios.request.mockImplementationOnce(signupApi);
@@ -68,7 +65,7 @@ describe('ManualRegistrationPage', () => {
const submitButton = getByTestId('submitButton');
await waitFor(() => fireEvent.press(submitButton));
- const nextPageText = queryByText(/Profile/i);
+ const nextPageText = queryByText(/Checkout/i);
expect(nextPageText).toBeFalsy();
});
@@ -79,7 +76,7 @@ describe('ManualRegistrationPage', () => {
Promise.reject({
status: 400,
response: {
- error: {
+ data: {
name: 'Wrong name',
email: alreadyRegistered,
password1: 'Wrong password',
@@ -102,7 +99,7 @@ describe('ManualRegistrationPage', () => {
const submitButton = getByTestId('submitButton');
await waitFor(() => fireEvent.press(submitButton));
- const nextPageText = queryByText(/Profile/i);
+ const nextPageText = queryByText(/Checkout/i);
expect(nextPageText).toBeFalsy();
});
});
diff --git a/src/scenes/cart/Checkout/index.tsx b/src/scenes/cart/Checkout/index.tsx
index bca12b8f080983ff579d2bf35f2f9a815e19bd4d..9fceb157d51b40f80fe70c0d9b64f247e525d7b1 100644
--- a/src/scenes/cart/Checkout/index.tsx
+++ b/src/scenes/cart/Checkout/index.tsx
@@ -26,7 +26,7 @@ const Checkout: FC = () => {
const { isLoading, data } = useApi(fetchCart);
- useAuthGuardEffect();
+ useAuthGuardEffect(true);
if (isLoading) {
return ;
diff --git a/src/scenes/cart/ChoosePlan/index.tsx b/src/scenes/cart/ChoosePlan/index.tsx
index 8807a4566621f73a7bd007e0fdc79b89d847208a..2c7da9fad0e85649c6c1e99f6418a7a1c58491d6 100644
--- a/src/scenes/cart/ChoosePlan/index.tsx
+++ b/src/scenes/cart/ChoosePlan/index.tsx
@@ -25,7 +25,7 @@ const ChoosePlan: FC = () => {
const [currentPage, setCurrentPage] = useState(1);
const [isSubmitting, setIsSubmitting] = useState(false);
const [values, setValues] = useState(initialValues);
- const [programs, setPrograms] = useState(defaultProgramRecommendations);
+ const [programs, setPrograms] = useState(undefined);
const handleSubmit = async () => {
setIsSubmitting(true);
@@ -73,7 +73,7 @@ const ChoosePlan: FC = () => {
retrieveNutritionistsApi,
);
- if (isLoading) {
+ if (isLoading || programs === undefined) {
return ;
}
return (
diff --git a/src/scenes/common/InitialPage/index.test.tsx b/src/scenes/common/InitialPage/index.test.tsx
index 952fc9528c8e918bfe6184fe8069d42af416d95f..9d43f65059a09aaa931ba3a7fae85f220d70f51c 100644
--- a/src/scenes/common/InitialPage/index.test.tsx
+++ b/src/scenes/common/InitialPage/index.test.tsx
@@ -1,54 +1,46 @@
import React from 'react';
-import { fireEvent, render } from '@testing-library/react-native';
+import { render, fireEvent, waitFor } from 'utils/testing';
import * as ROUTES from 'constants/routes';
import InitialPage from '.';
-const createTestProps = (props: Object) => ({
- navigation: {
- navigate: jest.fn(),
- },
- ...props,
-});
-
describe('InitialPage', () => {
- it('renders correctly', () => {
- render();
+ test('shows dietela cover loader when is loading', async () => {
+ const { queryByTestId } = render(, ROUTES.initial, {
+ isFirstLoading: true,
+ });
+
+ expect(queryByTestId('background')).toBeFalsy();
});
test('has background image', () => {
- const { queryByTestId } = render();
+ const { queryByTestId } = render(, ROUTES.initial);
expect(queryByTestId('background')).toBeTruthy();
});
test('has Dietela logo', () => {
- const { queryByTestId } = render();
+ const { queryByTestId } = render(, ROUTES.initial);
expect(queryByTestId('logo')).toBeTruthy();
});
test('has call-to-action button that navigates to Dietela Quiz', () => {
- let props = createTestProps({});
- const { getByText } = render();
-
- const button = getByText(/konsultasi sekarang/i);
- fireEvent.press(button);
+ const { getByText, queryByText } = render(, ROUTES.initial);
+ expect(queryByText(/konsultasi sekarang/i)).toBeTruthy();
+ fireEvent.press(getByText(/konsultasi sekarang/i));
- expect(button).toBeTruthy();
- expect(props.navigation.navigate).toHaveBeenCalledWith(
- ROUTES.allAccessQuestionnaire,
- );
+ expect(queryByText(/Dietela Quiz/i)).toBeTruthy();
});
- test('has link button that navigates to Login Page', () => {
- let props = createTestProps({});
- const { getByText } = render();
-
- const link = getByText(/Login disini/i);
- fireEvent.press(link);
+ test('has link button that navigates to Login Page', async () => {
+ const { getByText, queryByText, queryAllByText } = render(
+ ,
+ ROUTES.initial,
+ );
+ expect(queryByText(/Login disini/i)).toBeTruthy();
+ await waitFor(() => fireEvent.press(getByText(/Login disini/i)));
- expect(link).toBeTruthy();
- expect(props.navigation.navigate).toHaveBeenCalledWith(ROUTES.login);
+ expect(queryAllByText(/Login/i)).toBeTruthy();
});
});
diff --git a/src/scenes/common/InitialPage/index.tsx b/src/scenes/common/InitialPage/index.tsx
index cc49653b62508f2a1ab7ba8a90603792761d69a7..58cad2d0a51243a566695621f70b43857f9a686c 100644
--- a/src/scenes/common/InitialPage/index.tsx
+++ b/src/scenes/common/InitialPage/index.tsx
@@ -1,46 +1,57 @@
-import React, { FC } from 'react';
-
+import React, { FC, useContext } from 'react';
+import { useNavigation } from '@react-navigation/native';
import { View, Text, ImageBackground, Image } from 'react-native';
-import { BigButton, Link } from 'components/core';
+import { BigButton, Link, DietelaCoverLoader } from 'components/core';
+import { banner_girl_eating, logo_white_small } from 'assets/images';
+import { useAuthEffect } from 'hooks';
import * as ROUTES from 'constants/routes';
-
+import { UserContext } from 'provider';
import { layoutStyles, typographyStyles } from 'styles';
+
import { styles } from './styles';
-import { banner_girl_eating, logo_white_small } from 'assets/images';
+const InitialPage: FC = () => {
+ const navigation = useNavigation();
+ const { isFirstLoading } = useContext(UserContext);
-const InitialPage: FC = ({ navigation }) => (
-
-
-
-
-
- Online Nutritionist Pertama di Indonesia
-
-
- Hadir untuk mendefinisikan ulang kata “Diet” untuk Anda!
-
-
- Apapun masalah diet Anda, konsultasikan bersama kami!
-
-
-
- navigation.navigate(ROUTES.allAccessQuestionnaire)}
- />
- navigation.navigate(ROUTES.login)}
- />
+ useAuthEffect();
+
+ if (isFirstLoading) {
+ return ;
+ }
+ return (
+
+
+
+
+
+ Online Nutritionist Pertama di Indonesia
+
+
+ Hadir untuk mendefinisikan ulang kata “Diet” untuk Anda!
+
+
+ Apapun masalah diet Anda, konsultasikan bersama kami!
+
+
+
+ navigation.navigate(ROUTES.allAccessQuestionnaire)}
+ />
+ navigation.navigate(ROUTES.login)}
+ />
+
-
-
-);
+
+ );
+};
export default InitialPage;
diff --git a/src/services/auth/index.ts b/src/services/auth/index.ts
index ff598c35840338ac7452432177b9b2b1d652425a..e212d20d490fab191f68ee4829db4ae61823e40f 100644
--- a/src/services/auth/index.ts
+++ b/src/services/auth/index.ts
@@ -6,6 +6,9 @@ import {
LoginRequest,
LoginResponse,
RegistrationRequest,
+ User,
+ LinkUserDataRequest,
+ LinkUserDataResponse,
} from './models';
export const googleLoginApi = (
@@ -23,3 +26,13 @@ export const signupApi = (
export const loginApi = (body: LoginRequest): ApiResponse => {
return api(RequestMethod.POST, apiUrls.login, body);
};
+
+export const retrieveUserApi = (): ApiResponse => {
+ return api(RequestMethod.GET, apiUrls.user);
+};
+
+export const linkUserDataApi = (
+ body: LinkUserDataRequest,
+): ApiResponse => {
+ return api(RequestMethod.POST, apiUrls.linkData, body);
+};
diff --git a/src/services/auth/models.ts b/src/services/auth/models.ts
index 6c24a9a0c55c5709bc2ed7aa7cd516df19b243ea..7b1edd7a5e3523d530eea1ff665395d0b941129b 100644
--- a/src/services/auth/models.ts
+++ b/src/services/auth/models.ts
@@ -1,3 +1,6 @@
+import { DietProfileResponse } from 'services/dietelaQuiz/models';
+import { CartResponse } from 'services/payment/models';
+
export interface GoogleLoginRequest {
access_token: string;
}
@@ -9,18 +12,23 @@ export interface RegistrationRequest {
password2: string;
}
-export type Role = 'client' | 'nutritionist' | 'admin';
-
-export interface LoginRequest {
- email: string;
- password: string;
- role: Role;
+export enum UserRole {
+ CLIENT = 'client',
+ NUTRITIONIST = 'nutritionist',
+ ADMIN = 'admin',
}
export interface User {
id: number | null;
email: string;
name: string;
+ role: UserRole | null;
+}
+
+export interface LoginRequest {
+ email: string;
+ password: string;
+ role: UserRole;
}
export interface LoginResponse {
@@ -28,3 +36,15 @@ export interface LoginResponse {
refresh_token: string;
user: User;
}
+
+export interface LinkUserDataRequest {
+ email: string;
+ diet_profile_id: number;
+ cart_id: number;
+}
+
+export interface LinkUserDataResponse {
+ user: User;
+ diet_profile: DietProfileResponse;
+ cart: CartResponse;
+}
diff --git a/src/services/auth/urls.ts b/src/services/auth/urls.ts
index 039677ed29a3fc6ab51adf850977d68f3239e87c..31f21fa0935ba5b43151740a367f3a1b70486350 100644
--- a/src/services/auth/urls.ts
+++ b/src/services/auth/urls.ts
@@ -3,3 +3,6 @@ const auth = 'auth/';
export const google = `${auth}google/`;
export const signup = `${auth}registration/`;
export const login = `${auth}user-login/`;
+
+export const user = `${auth}user/`;
+export const linkData = `${auth}link-data/`;
diff --git a/src/utils/testing.tsx b/src/utils/testing.tsx
index 6d9a987472526f6ed99db1131435e76a232ec518..0fd9eaa07cdc8b36a28f2e31179070f5b0d2a2e0 100644
--- a/src/utils/testing.tsx
+++ b/src/utils/testing.tsx
@@ -4,8 +4,8 @@ import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { navigation } from 'constants/navigation';
-import ContextProvider from 'provider';
-import { UserContext, useUserContext } from 'provider/UserContext';
+import { UserContext } from 'provider';
+import { UserRole } from 'services/auth/models';
const Stack = createStackNavigator();
@@ -16,34 +16,44 @@ interface TestProvider {
children: ReactElement;
}
-const WithAuthProvider: FC = ({ children }) => {
- const user = useUserContext();
-
- return (
-
- {children}
-
- );
-};
-
const TestProvider: FC = ({
route,
params,
isAuth,
children,
}) => {
- const Provider = isAuth ? WithAuthProvider : ContextProvider;
+ const user = isAuth
+ ? {
+ id: 1,
+ email: 'user.test@gmail.com',
+ name: 'User Test',
+ role: UserRole.CLIENT,
+ }
+ : {
+ id: null,
+ email: '',
+ name: '',
+ role: null,
+ };
+
+ const mockFn = (_?: any) => Promise.reject();
+
return (
-
+
{navigation.map((nav, i) => (
@@ -60,7 +70,7 @@ const TestProvider: FC = ({
))}
-
+
);
};