Fakultas Ilmu Komputer UI

Commit b87e5675 authored by Doan Andreas Nathanael's avatar Doan Andreas Nathanael
Browse files

Manual Registration API Integration

parent 717e5abe
import { LoginResponse } from 'services/auth/models';
export const validRegistrationValues: { [_: string]: any } = {
name: 'Doan Didinding',
email: 'doan@dietela.com',
password1: 'g8ake1afig',
password2: 'g8ake1afig',
};
export const invalidRegistrationValues: { [_: string]: any } = {
name: 'Doan Didinding',
email: 'doan@dietela.com',
password1: '12345678',
password2: '12345678',
};
export const authResponse: LoginResponse = {
access_token: 'ax41faf',
refresh_token: '9tka0kfa',
user: {
id: 1,
email: validRegistrationValues.email,
name: validRegistrationValues.name,
},
};
......@@ -19,7 +19,7 @@ const App: FC = () => {
<ContextProvider>
<NavigationContainer>
<Stack.Navigator
initialRouteName={ROUTES.login}
initialRouteName={ROUTES.registration}
screenOptions={screenOptions}>
{navigation.map((nav, i) => (
<Stack.Screen
......
......@@ -13,7 +13,7 @@ const BigButton: FC<Props> = ({
testID,
}) => (
<Button
titleStyle={[typographyStyles.overlineBig, styles.titleStyle]}
titleStyle={[typographyStyles.bodyMedium, styles.titleStyle]}
disabledTitleStyle={styles.disabledStyle}
disabled={disabled}
buttonStyle={styles.containerStyle}
......
......@@ -2,7 +2,7 @@ import { StyleSheet } from 'react-native';
import { colors } from 'styles';
export const styles = StyleSheet.create({
titleStyle: { color: colors.textBlack },
titleStyle: { color: colors.textBlack, textTransform: 'capitalize' },
containerStyle: {
padding: 12,
overflow: 'hidden',
......
import React from 'react';
import { render } from '@testing-library/react-native';
import Link from '.';
import { colors } from 'styles';
describe('Link', () => {
it('renders correctly', () => {
render(<Link title="hai" />);
});
it('renders correctly given color', () => {
render(<Link title="hai" color={colors.primary} />);
});
});
import React, { FC } from 'react';
import { StyleSheet } from 'react-native';
import { Button, ButtonProps } from 'react-native-elements';
import { colors } from 'styles';
interface Props extends ButtonProps {
title: string;
color?: string;
}
const Link: FC<Props> = ({ title, color, ...props }) => {
const styles = stylesWithColor(color);
return (
<Button
title={title}
type="clear"
buttonStyle={styles.buttonPadding}
titleStyle={styles.title}
{...props}
/>
);
};
const stylesWithColor = (color?: string) =>
StyleSheet.create({
buttonPadding: {
padding: 5,
},
title: {
textDecorationLine: 'underline',
color: color ? color : colors.buttonYellow,
},
});
export default Link;
......@@ -2,6 +2,7 @@ export { default as AutoImage } from './AutoImage';
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 ResultCard } from './ResultCard';
export { default as Statistic } from './Statistic';
......
......@@ -4,8 +4,8 @@ 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 } from 'services/auth';
import { User } from 'services/auth/models';
import { googleLoginApi, signupApi } from 'services/auth';
import { User, RegistrationRequest } from 'services/auth/models';
import { set401Callback, setAuthHeader, resetAuthHeader } from 'services/api';
import { iUserContext } from './types';
......@@ -38,8 +38,18 @@ export const useUserContext = (): iUserContext => {
}
}, []);
// TODO
const signup = async () => {};
const signup = async (registerData: RegistrationRequest) => {
const response = await signupApi(registerData);
if (response.success && response.data) {
await setCache(CACHE_KEYS.authToken, response.data?.access_token);
await setCache(CACHE_KEYS.refreshToken, response.data?.refresh_token);
setUser(response.data.user);
}
return response;
};
// TODO
const login = async () => {};
......
import { User } from 'services/auth/models';
import { ApiResponse } from 'services/api';
import { LoginResponse, RegistrationRequest, User } from 'services/auth/models';
export interface iUserContext {
user: User;
isAuthenticated: boolean;
isLoading: boolean;
signup: () => Promise<void>;
signup: (data: RegistrationRequest) => ApiResponse<LoginResponse>;
login: () => Promise<void>;
loginWithGoogle: () => Promise<void>;
logout: () => Promise<void>;
......
import React from 'react';
import { render, fireEvent, waitFor } from 'utils/testing';
import * as ROUTES from 'constants/routes';
import axios from 'axios';
import ManualRegistrationPage from '.';
import { textField } from './schema';
import {
authResponse,
validRegistrationValues,
invalidRegistrationValues,
} from '__mocks__/auth';
jest.mock('react-native-toast-message');
jest.mock('axios');
const mockAxios = axios as jest.Mocked<typeof axios>;
describe('ManualRegistrationPage', () => {
it('renders correctly', () => {
render(<ManualRegistrationPage />, ROUTES.registration);
});
it('success when field is valid and submit success', async () => {
const signupApi = () =>
Promise.resolve({
status: 201,
data: authResponse,
});
mockAxios.request.mockImplementationOnce(signupApi);
const { getByPlaceholderText, queryByText, getByTestId } = render(
<ManualRegistrationPage />,
ROUTES.registration,
);
textField.map(({ name, placeholder }) => {
const formField = getByPlaceholderText(placeholder as string);
fireEvent.changeText(formField, validRegistrationValues[name]);
});
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 () => {
const signupApi = () =>
Promise.reject({
status: 400,
response: {
error: 'error',
},
});
mockAxios.request.mockImplementationOnce(signupApi);
const { getByPlaceholderText, queryByText, getByTestId } = render(
<ManualRegistrationPage />,
ROUTES.registration,
);
textField.map(({ name, placeholder }) => {
const formField = getByPlaceholderText(placeholder as string);
fireEvent.changeText(formField, validRegistrationValues[name]);
});
const submitButton = getByTestId('submitButton');
await waitFor(() => fireEvent.press(submitButton));
const nextPageText = queryByText(/Profile/i);
expect(nextPageText).toBeFalsy();
});
it('fails when field is invalid and submit success', async () => {
const alreadyRegistered = 'Email is already registered';
const signupApi = () =>
Promise.reject({
status: 400,
response: {
error: {
name: 'Wrong name',
email: alreadyRegistered,
password1: 'Wrong password',
password2: 'Wrong password',
},
},
});
mockAxios.request.mockImplementationOnce(signupApi);
const { getByPlaceholderText, queryByText, getByTestId } = render(
<ManualRegistrationPage />,
ROUTES.registration,
);
textField.map(({ name, placeholder }) => {
const formField = getByPlaceholderText(placeholder as string);
fireEvent.changeText(formField, invalidRegistrationValues[name]);
});
const submitButton = getByTestId('submitButton');
await waitFor(() => fireEvent.press(submitButton));
const nextPageText = queryByText(/Profile/i);
expect(nextPageText).toBeFalsy();
});
});
import React, { FC } from 'react';
import { useForm } from 'hooks';
import React, { FC, useContext } from 'react';
import { useAuthEffect, useForm } from 'hooks';
import { ScrollView } from 'react-native-gesture-handler';
import { BigButton, Toast } from 'components/core';
import { TextField } from 'components/form';
import { fieldValidation, initialValues, textField } from './schema';
import { generateValidationSchema } from 'utils/form';
import { TextField } from 'components/form';
import { ScrollView } from 'react-native-gesture-handler';
import { UserContext } from 'provider';
import { layoutStyles } from 'styles';
import { BigButton } from 'components/core';
import { GoogleLoginButton } from '../components';
import { Section } from 'components/layout';
const isPasswordField = (name: string) =>
name === 'password1' || name === 'password2';
const ManualRegistrationPage: FC = () => {
const { getTextInputProps, handleSubmit, isSubmitting } = useForm({
const { signup, loginWithGoogle, isLoading } = useContext(UserContext);
const {
getTextInputProps,
handleSubmit,
isSubmitting,
setFieldError,
} = useForm({
initialValues,
validationSchema: generateValidationSchema(fieldValidation),
onSubmit: async (values) => {
console.log(values);
const response = await signup(values);
if (!response.success) {
setFieldError('name', response.error.name);
setFieldError('email', response.error.email);
setFieldError('password1', response.error.password1);
setFieldError('password2', response.error.password2);
Toast.show({
type: 'error',
text1: 'Gagal registrasi akun',
text2: 'Terjadi kesalahan registrasi. Silakan coba lagi',
});
}
},
});
useAuthEffect();
return (
<ScrollView contentContainerStyle={layoutStyles}>
{textField.map((fieldProps, i) => {
return (
<TextField
key={`field${i}`}
label={fieldProps.label}
required={fieldProps.required}
placeholder={fieldProps.placeholder}
{...getTextInputProps(fieldProps.name)}
secureTextEntry={isPasswordField(fieldProps.name)}
/>
);
})}
{textField.map((fieldProps, i) => (
<TextField
key={`field${i}`}
label={fieldProps.label}
required={fieldProps.required}
placeholder={fieldProps.placeholder}
{...getTextInputProps(fieldProps.name)}
secureTextEntry={isPasswordField(fieldProps.name)}
/>
))}
<BigButton
title="daftarkan akun"
onPress={handleSubmit}
loading={isSubmitting}
testID="submitButton"
/>
<Section>
<GoogleLoginButton onPress={loginWithGoogle} isLoading={isLoading} />
</Section>
</ScrollView>
);
};
......
import { RegistrationRequest } from 'services/auth/models';
import { TextFieldSchema } from 'types/form';
import { FieldType, FieldValidation } from 'utils/form';
......@@ -28,7 +29,7 @@ export const textField: TextFieldSchema[] = [
},
];
export const initialValues = {
export const initialValues: RegistrationRequest = {
name: '',
email: '',
password1: '',
......
......@@ -10,10 +10,10 @@ const GoogleLoginButton: FC<Props> = ({ onPress, isLoading }) => (
title="Lanjut dengan Google"
icon={<Image source={googleLogo} style={styles.googleIcon} />}
onPress={onPress}
containerStyle={styles.container}
buttonStyle={styles.button}
titleStyle={styles.title}
disabled={isLoading}
raised
/>
);
......
import { StyleSheet } from 'react-native';
import { typography } from 'styles';
import { colors, typography } from 'styles';
export const styles = StyleSheet.create({
googleIcon: {
......@@ -8,10 +8,15 @@ export const styles = StyleSheet.create({
},
button: {
backgroundColor: 'white',
borderColor: colors.border,
borderWidth: 0.5,
},
container: {
elevation: 1,
},
title: {
paddingLeft: 24,
color: 'black',
...typography.bodyLarge,
...typography.bodyMedium,
},
});
......@@ -40,4 +40,15 @@ describe('InitialPage', () => {
ROUTES.allAccessQuestionnaire,
);
});
test('has link button that navigates to Login Page', () => {
let props = createTestProps({});
const { getByText } = render(<InitialPage {...props} />);
const link = getByText(/Login disini/i);
fireEvent.press(link);
expect(link).toBeTruthy();
expect(props.navigation.navigate).toHaveBeenCalledWith(ROUTES.login);
});
});
import React, { FC } from 'react';
import { View, Text, ImageBackground, Image } from 'react-native';
import { layoutStyles, typographyStyles } from 'styles';
import { BigButton } from 'components/core';
import { BigButton, Link } from 'components/core';
import * as ROUTES from 'constants/routes';
import { layoutStyles, typographyStyles } from 'styles';
import { styles } from './styles';
import { banner_girl_eating, logo_white_small } from 'assets/images';
import * as ROUTES from 'constants/routes';
const InitialPage: FC = ({ navigation }) => (
<ImageBackground
......@@ -31,6 +34,10 @@ const InitialPage: FC = ({ navigation }) => (
title="konsultasi sekarang"
onPress={() => navigation.navigate(ROUTES.allAccessQuestionnaire)}
/>
<Link
title="Sudah punya akun? Login disini"
onPress={() => navigation.navigate(ROUTES.login)}
/>
</View>
</View>
</ImageBackground>
......
import React from 'react';
import { render, fireEvent } from 'utils/testing';
import * as ROUTES from 'constants/routes';
import ManualRegistrationPage from '.';
import { textField } from './schema';
import { validRegistrationValues } from '__mocks__/auth';
describe('ManualRegistrationPage', () => {
it('renders correctly', () => {
render(<ManualRegistrationPage />, ROUTES.registration);
});
it('success when all field is valid', async () => {
const { getByPlaceholderText, getByTestId } = render(
<ManualRegistrationPage />,
ROUTES.registration,
);
textField.map(({ name, placeholder }) => {
const formField = getByPlaceholderText(placeholder as string);
fireEvent.changeText(formField, validRegistrationValues[name]);
});
const submitButton = getByTestId('submitButton');
fireEvent.press(submitButton);
});
});
export { default as LoginPage } from './auth/Login';
export { default as ManualRegistrationPage } from './auth/ManualRegistrationPage';
export { default as InitialPage } from './common/InitialPage';
export { default as ComingSoonPage } from './common/ComingSoonPage';
export { default as ManualRegistrationPage } from './common/ManualRegistrationPage';
export { default as AllAccessQuestionnaire } from './questionnaire/AllAccessQuestionnaire';
export { default as DietelaQuizResult } from './questionnaire/DietelaQuizResult';
......
import { api, RequestMethod, ApiResponse } from '../api';
import * as apiUrls from './urls';
import { GoogleLoginRequest, GoogleLoginResponse } from './models';
import {
GoogleLoginRequest,
LoginResponse,
RegistrationRequest,
} from './models';
export const googleLoginApi = (
body: GoogleLoginRequest,
): ApiResponse<GoogleLoginResponse> => {
): ApiResponse<LoginResponse> => {
return api(RequestMethod.POST, apiUrls.google, body);
};
export const signupApi = (
body: RegistrationRequest,
): ApiResponse<LoginResponse> => {
return api(RequestMethod.POST, apiUrls.signup, body);
};
......@@ -2,13 +2,20 @@ export interface GoogleLoginRequest {
access_token: string;
}
export interface RegistrationRequest {
name: string;
email: string;
password1: string;
password2: string;
}
export interface User {
id: number | null;
email: string;
name: string;
}
export interface GoogleLoginResponse {
export interface LoginResponse {
access_token: string;
refresh_token: string;
user: User;
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment