Fakultas Ilmu Komputer UI

Commit 8dd444bf authored by Doan Andreas Nathanael's avatar Doan Andreas Nathanael
Browse files

Nutritionist & Admin Login UI & API Integration

parent 7c265074
......@@ -12,6 +12,7 @@ import {
NutritionistDetail,
PaymentResult,
ExtendedQuestionnaire,
NutritionistAdminLogin,
} from 'scenes';
import { FC } from 'react';
import {
......@@ -73,6 +74,11 @@ export const publicNavigation: NavRoute[] = [
component: LoginPage,
header: 'Login',
},
{
name: ROUTES.nutritionistAdminLogin,
component: NutritionistAdminLogin,
header: 'Login Tim Dietela',
},
];
export const unpaidClientNavigation: NavRoute[] = [
......@@ -152,6 +158,11 @@ export const testNavigation: NavRoute[] = [
component: LoginPage,
header: 'Login',
},
{
name: ROUTES.nutritionistAdminLogin,
component: NutritionistAdminLogin,
header: 'Login Tim Dietela',
},
{
name: ROUTES.checkout,
component: Checkout,
......
......@@ -16,6 +16,7 @@ export const nutritionistDetail = `${checkout}/nutritionist`;
export const registration = 'registration';
export const login = 'login';
export const nutritionistAdminLogin = 'nutritionist-admin-login';
const profile = 'profile';
export const clientProfile = `${profile}/client`;
......
......@@ -72,4 +72,18 @@ describe('Login page', () => {
afterAll(() => {
jest.clearAllMocks();
});
test('has link button that navigates to Nutritionist/Admin Login Page', async () => {
const { getByText, queryByText, queryAllByText } = render(
<Login />,
ROUTES.login,
);
expect(queryByText(/Login sebagai Nutrisionis/i)).toBeTruthy();
await waitFor(() =>
fireEvent.press(getByText(/Login sebagai Nutrisionis/i)),
);
expect(queryAllByText(/Login Tim Dietela/i)).toBeTruthy();
});
});
......@@ -4,19 +4,22 @@ import { UserContext } from 'provider';
import { useForm } from 'hooks';
import { GoogleLoginButton } from '../components';
import { BigButton, Toast } from 'components/core';
import { BigButton, Link, Toast } from 'components/core';
import { fieldValidation, initialValues, textField } from './schema';
import { generateValidationSchema } from 'utils/form';
import { layoutStyles } from 'styles';
import { TextField } from 'components/form';
import { Section } from 'components/layout';
import { useNavigation } from '@react-navigation/core';
import * as ROUTES from 'constants/routes';
const isPasswordField = (name: string) => name === 'password';
const Login: FC = () => {
const { login, isLoading, loginWithGoogle } = useContext(UserContext);
const [nonFieldError, setNonFieldError] = useState<string | null>();
const navigation = useNavigation();
const {
getTextInputProps,
......@@ -71,6 +74,12 @@ const Login: FC = () => {
<Section>
<GoogleLoginButton onPress={loginWithGoogle} isLoading={isLoading} />
</Section>
<Section>
<Link
title="Login sebagai Nutrisionis/Admin"
onPress={() => navigation.navigate(ROUTES.nutritionistAdminLogin)}
/>
</Section>
</View>
);
};
......
import { LoginRequest, Role } from 'services/auth/models';
import { LoginRequest, UserRole } from 'services/auth/models';
import { TextFieldSchema } from 'types/form';
import { FieldType, FieldValidation } from 'utils/form';
......@@ -20,7 +20,7 @@ export const textField: TextFieldSchema[] = [
export const initialValues: LoginRequest = {
email: '',
password: '',
role: 'client',
role: UserRole.CLIENT,
};
export const fieldValidation: FieldValidation[] = [
......@@ -38,4 +38,4 @@ export const fieldValidation: FieldValidation[] = [
},
];
export const setRole = (role: Role) => (initialValues.role = role);
export const setRole = (role: UserRole) => (initialValues.role = role);
import React from 'react';
import { render, fireEvent, waitFor } from 'utils/testing';
import * as ROUTES from 'constants/routes';
import axios from 'axios';
import NutritionistAdminLogin from '.';
import { authResponse, validLoginValues } from '__mocks__/auth';
import { textField } from './schema';
jest.mock('react-native-toast-message');
jest.mock('axios');
const mockAxios = axios as jest.Mocked<typeof axios>;
describe('NutritionistAdminLogin', () => {
it('renders correctly', () => {
render(<NutritionistAdminLogin />, ROUTES.nutritionistAdminLogin);
});
it('success when field is valid and submit success', async () => {
const loginApi = () =>
Promise.resolve({
status: 201,
data: authResponse,
});
mockAxios.request.mockImplementationOnce(loginApi);
const { getByPlaceholderText, getByTestId } = render(
<NutritionistAdminLogin />,
ROUTES.nutritionistAdminLogin,
);
textField.map(({ name, placeholder }) => {
const formField = getByPlaceholderText(placeholder as string);
fireEvent.changeText(formField, validLoginValues[name]);
});
const loginButton = getByTestId('timDietelaLogin');
await waitFor(() => fireEvent.press(loginButton));
});
it('fails when field is valid and submit fails', async () => {
const loginApi = () =>
Promise.reject({
status: 400,
response: {
data: 'error',
},
});
mockAxios.request.mockImplementationOnce(loginApi);
const { getByPlaceholderText, queryByText, getByTestId } = render(
<NutritionistAdminLogin />,
ROUTES.nutritionistAdminLogin,
);
textField.map(({ name, placeholder }) => {
const formField = getByPlaceholderText(placeholder as string);
fireEvent.changeText(formField, validLoginValues[name]);
});
const loginButton = getByTestId('timDietelaLogin');
await waitFor(() => fireEvent.press(loginButton));
const toastWarning = queryByText(/Profile/i);
expect(toastWarning).toBeFalsy();
});
afterAll(() => {
jest.clearAllMocks();
});
test('has link button that navigates to Nutritionist/Admin Login Page', async () => {
const { getByText, queryByText, queryAllByText } = render(
<NutritionistAdminLogin />,
ROUTES.nutritionistAdminLogin,
);
expect(queryByText(/Login sebagai Pengguna/i)).toBeTruthy();
await waitFor(() => fireEvent.press(getByText(/Login sebagai Pengguna/i)));
expect(queryAllByText(/Login/i)).toBeTruthy();
});
});
import React, { FC, useContext, useState } from 'react';
import { ScrollView, StyleSheet, Text } from 'react-native';
import { UserContext } from 'provider';
import { useForm } from 'hooks';
import { BigButton, Link, Toast } from 'components/core';
import {
fieldValidation,
initialValues,
radioButtonGroups,
textField,
} from './schema';
import { generateValidationSchema } from 'utils/form';
import { layoutStyles } from 'styles';
import { RadioButtonGroup, TextField } from 'components/form';
import { Section } from 'components/layout';
import { useNavigation } from '@react-navigation/core';
import * as ROUTES from 'constants/routes';
const isPasswordField = (name: string) => name === 'password';
const NutritionistAdminLogin: FC = () => {
const { login } = useContext(UserContext);
const [nonFieldError, setNonFieldError] = useState<string | null>();
const navigation = useNavigation();
const {
getTextInputProps,
getFormFieldProps,
handleSubmit,
isSubmitting,
setFieldError,
} = useForm({
initialValues,
validationSchema: generateValidationSchema(fieldValidation),
onSubmit: async (values) => {
const response = await login(values);
if (!response.success) {
const error = response.error;
setFieldError('email', error.email);
setFieldError('password', error.password);
setNonFieldError(error.non_field_errors);
Toast.show({
type: 'error',
text1: 'Gagal login akun',
text2: 'Terjadi kesalahan login. Silakan coba lagi',
});
}
},
});
return (
<ScrollView contentContainerStyle={layoutStyles}>
{textField.map(({ name, label, required, placeholder }, i) => (
<TextField
key={`field${i}`}
label={label}
required={required}
placeholder={placeholder}
{...getTextInputProps(name)}
secureTextEntry={isPasswordField(name)}
/>
))}
{radioButtonGroups.map((fieldProps, i) => (
<RadioButtonGroup
{...fieldProps}
{...getFormFieldProps(fieldProps.name)}
key={`radiobuttongroup${i}`}
/>
))}
{nonFieldError && (
<Text style={styles.nonfieldError}>{nonFieldError}</Text>
)}
<Section>
<BigButton
title="login"
onPress={handleSubmit}
loading={isSubmitting}
testID="timDietelaLogin"
/>
</Section>
<Section>
<Link
title="Login sebagai Pengguna"
onPress={() => navigation.navigate(ROUTES.login)}
/>
</Section>
</ScrollView>
);
};
const styles = StyleSheet.create({
nonfieldError: { color: 'red' },
});
export default NutritionistAdminLogin;
import { LoginRequest, UserRole } from 'services/auth/models';
import { RadioButtonGroupSchema, TextFieldSchema } from 'types/form';
import { FieldType, FieldValidation } from 'utils/form';
export const textField: TextFieldSchema[] = [
{
label: 'Email address',
placeholder: 'Masukkan email Anda',
required: true,
name: 'email',
},
{
label: 'Password',
placeholder: 'Masukkan password Anda',
required: true,
name: 'password',
},
];
export const radioButtonGroups: RadioButtonGroupSchema[] = [
{
label: 'Role',
required: true,
name: 'role',
choices: [
{
value: UserRole.NUTRITIONIST,
label: 'Nutrisionis',
},
{
value: UserRole.ADMIN,
label: 'Admin',
},
],
},
];
export const initialValues: LoginRequest = {
email: '',
password: '',
role: UserRole.NUTRITIONIST,
};
export const fieldValidation: FieldValidation[] = [
{
name: 'email',
required: true,
label: 'Email address',
type: FieldType.EMAIL,
},
{
name: 'password',
required: true,
label: 'Password',
type: FieldType.PASSWORD,
},
];
export const setRole = (role: UserRole) => (initialValues.role = role);
export { default as LoginPage } from './auth/Login';
export { default as NutritionistAdminLogin } from './auth/NutritionistAdminLogin';
export { default as ManualRegistrationPage } from './auth/ManualRegistrationPage';
export { default as InitialPage } from './common/InitialPage';
......
Markdown is supported
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