Fakultas Ilmu Komputer UI

Commit 7c265074 authored by Wulan Mantiri's avatar Wulan Mantiri
Browse files

Merge branch 'PBI-9-diet_questionnaire_ui_and_validation' into 'staging'

Implement diet questionnaire UI layout and form validation

See merge request !41
parents 012d6878 0be41e76
Pipeline #75934 passed with stages
in 84 minutes and 46 seconds
import 'react-native-gesture-handler';
import 'dayjs/locale/id';
import { AppRegistry } from 'react-native';
import App from './src/app';
import { name as appName } from './app.json';
......
......@@ -3,6 +3,7 @@ import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { ThemeProvider } from 'react-native-elements';
import Toast from 'react-native-toast-message';
import dayjs from 'dayjs';
import { DietelaCoverLoader } from 'components/core';
import ContextProvider, { UserContext } from 'provider';
......@@ -12,17 +13,14 @@ import { screenOptions, toastConfig } from './styles';
import LogoutButton from './LogoutButton';
import { getNavigation } from './schema';
dayjs.locale('id');
const Stack = createStackNavigator();
const NavigationStack: FC = () => {
const { isAuthenticated, isUnpaidClient, isFirstLoading } = useContext(
UserContext,
);
const { isAuthenticated, user, isFirstLoading } = useContext(UserContext);
const { initialRoute, navigation } = getNavigation(
isAuthenticated,
isUnpaidClient,
);
const { initialRoute, navigation } = getNavigation(isAuthenticated, user);
if (isFirstLoading) {
return <DietelaCoverLoader />;
......
import * as ROUTES from 'constants/routes';
import {
publicNavigation,
privateNavigation,
unpaidClientNavigation,
onboardingClientNavigation,
clientNavigation,
} from 'constants/navigation';
import { User, UserRole } from 'services/auth/models';
import { TransactionStatus } from 'services/payment/models';
export const getNavigation = (
isAuthenticated: boolean,
isUnpaidClient: boolean,
) => {
export const getNavigation = (isAuthenticated: boolean, user: User) => {
if (isAuthenticated) {
return isUnpaidClient
? {
if (user.role === UserRole.CLIENT) {
if (user.transaction_status === TransactionStatus.UNPAID) {
return {
initialRoute: ROUTES.checkout,
navigation: unpaidClientNavigation,
}
: {
initialRoute: ROUTES.profile,
navigation: privateNavigation,
};
} else if (!user.has_profile) {
return {
initialRoute: ROUTES.extendedQuestionnaire,
navigation: onboardingClientNavigation,
};
}
return {
initialRoute: ROUTES.clientProfile,
navigation: clientNavigation,
};
}
}
return {
initialRoute: ROUTES.initial,
......
......@@ -14,7 +14,6 @@ const BigButton: FC<Props> = ({
}) => (
<Button
titleStyle={[typographyStyles.bodyMedium, styles.titleStyle]}
disabledTitleStyle={styles.disabledStyle}
disabled={disabled}
buttonStyle={styles.containerStyle}
onPress={onPress}
......
......@@ -9,5 +9,4 @@ export const styles = StyleSheet.create({
borderRadius: 4,
backgroundColor: colors.buttonYellow,
},
disabledStyle: { color: 'white' },
});
......@@ -2,6 +2,6 @@ export interface Props {
title: string;
onPress: () => void;
loading?: boolean;
disabled?: true;
disabled?: boolean;
testID?: string;
}
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import CheckboxGroup from '.';
import { checkbox } from 'constants/options';
describe('CheckboxGroup component', () => {
const props = {
choices: [
{
label: 'Choice 1',
value: 1,
},
{
label: 'Choice 2',
value: 2,
},
],
};
it('is able to select multiple', () => {
let value: Array<string | number> = [];
const { getByText } = render(
<CheckboxGroup
{...props}
value={value}
onChange={(v: typeof value) => (value = v)}
/>,
);
expect(value).toStrictEqual([]);
fireEvent.press(getByText(/Choice 1/i));
expect(value).toContain(1);
fireEvent.press(getByText(/Choice 2/i));
expect(value).toContain(2);
});
it('is able to unselect checkbox', () => {
let value: Array<string | number> = [2];
const { getByText } = render(
<CheckboxGroup
{...props}
value={value}
onChange={(v: typeof value) => (value = v)}
/>,
);
expect(value).toContain(2);
fireEvent.press(getByText(/Choice 2/i));
expect(value).toStrictEqual([]);
});
it('shows other options when hasOtherChoice prop is true', () => {
let value: Array<string | number> = [];
const { getByText } = render(
<CheckboxGroup
{...props}
value={value}
onChange={(v: typeof value) => (value = v)}
hasOtherChoice
/>,
);
const otherChoice = getByText(/Lainnya/i);
expect(otherChoice).toBeTruthy();
fireEvent.press(otherChoice);
expect(value).toContain(checkbox.OTHER);
});
});
import React, { FC } from 'react';
import { View } from 'react-native';
import { CheckBox, Text, Input } from 'react-native-elements';
import { typographyStyles } from 'styles';
import { styles } from './styles';
import { Props } from './types';
import FormLabel from '../FormLabel';
import { checkbox } from 'constants/options';
const CheckboxGroup: FC<Props> = ({
label,
choices,
value,
onChange,
hasOtherChoice,
otherValue,
setOtherValue,
}) => {
const handlePress = (choiceValue: string | number) => {
if (value.includes(choiceValue)) {
onChange(value.filter((item: number | string) => item !== choiceValue));
} else {
onChange([...value, choiceValue]);
}
};
return (
<View>
<FormLabel label={label} />
<Text style={[typographyStyles.bodySmall, styles.helperText]}>
Dapat memilih lebih dari satu
</Text>
{choices.map((choice) => (
<CheckBox
key={choice.value}
title={choice.label}
checked={value.includes(choice.value)}
onPress={() => handlePress(choice.value)}
/>
))}
{hasOtherChoice ? (
<CheckBox
title={
<View style={styles.otherContainer}>
<Text style={styles.textStyle}>Lainnya:</Text>
<Input
onChangeText={setOtherValue}
value={otherValue}
inputContainerStyle={styles.inputContainerStyle}
labelStyle={styles.removed}
errorStyle={styles.removed}
/>
</View>
}
checked={value.includes(checkbox.OTHER)}
onPress={() => handlePress(checkbox.OTHER)}
/>
) : null}
</View>
);
};
export default CheckboxGroup;
import { StyleSheet } from 'react-native';
import { colors, typography } from 'styles';
export const styles = StyleSheet.create({
helperText: {
color: colors.formLabel,
marginBottom: 6,
},
otherContainer: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 12,
width: '76%',
},
textStyle: {
fontWeight: '400',
...typography.bodyMedium,
marginRight: 12,
},
inputContainerStyle: {
height: 22,
},
removed: {
height: 0,
marginVertical: 0,
marginHorizontal: 0,
paddingVertical: 0,
},
});
import { Choice } from '../MultipleChoice/types';
import { Props as FormLabelProps } from '../FormLabel/types';
export interface Props extends FormLabelProps {
choices: Choice[];
value: any;
onChange: (_: any) => void;
hasOtherChoice?: boolean;
otherValue?: string;
setOtherValue?: (_: string | number) => void;
}
import React from 'react';
import { render } from '@testing-library/react-native';
import Datepicker from '.';
describe('Datepicker component', () => {
let value: Date = new Date();
const props = {
value,
onChange: (v: Date) => (value = v),
};
it('renders correctly', () => {
render(<Datepicker {...props} />);
});
it('renders correctly with error message', () => {
render(<Datepicker {...props} errorMessage="error" />);
});
it('opens dialog when pressed', () => {
const { getByTestId } = render(<Datepicker {...props} />);
const datepickerButton = getByTestId('datepickertrigger');
expect(datepickerButton).toBeTruthy();
});
});
import React, { FC, useState } from 'react';
import RNDateTimePicker from '@react-native-community/datetimepicker';
import { View, TouchableOpacity } from 'react-native';
import { Text, Icon } from 'react-native-elements';
import { typographyStyles } from 'styles';
import { styles } from './styles';
import { Props } from './types';
import FormLabel from '../FormLabel';
import { convertDate } from 'utils/format';
const Datepicker: FC<Props> = ({
label,
required,
errorMessage,
value,
onChange,
}) => {
const [show, setShow] = useState(false);
const onPress = () => setShow(true);
const handleChange = (_: any, date?: Date) => {
setShow(false);
onChange(date);
};
return (
<View style={styles.spacing}>
<FormLabel label={label} required={required} />
<TouchableOpacity
style={styles.container}
onPress={onPress}
testID="datepickertrigger">
<Text style={typographyStyles.bodyMedium}>{convertDate(value)}</Text>
<Icon name="calendar" type="material-community" />
</TouchableOpacity>
{errorMessage ? (
<Text style={[typographyStyles.caption, styles.red]}>
{errorMessage}
</Text>
) : null}
{show ? (
<RNDateTimePicker
value={value}
onChange={handleChange}
locale="id-ID"
/>
) : null}
</View>
);
};
export default Datepicker;
import { StyleSheet } from 'react-native';
import { colors } from 'styles';
export const styles = StyleSheet.create({
spacing: {
marginBottom: 24,
},
red: {
color: 'red',
marginLeft: 4,
marginTop: 4,
},
container: {
borderRadius: 4,
borderWidth: 1,
borderColor: colors.border,
paddingHorizontal: 16,
paddingVertical: 12,
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
placeholder: {
color: colors.formLabel,
},
});
import { Props as FormLabelProps } from '../FormLabel/types';
export interface Props extends FormLabelProps {
placeholder?: string;
errorMessage?: any;
value: any;
onChange: (_: any) => void;
}
......@@ -6,7 +6,7 @@ export const styles = StyleSheet.create({
color: 'red',
},
label: {
fontWeight: '600',
color: 'black',
marginBottom: 4,
...typography.bodyMedium,
},
......
import { ReactNode } from 'react';
export interface Props {
label?: ReactNode;
label?: string;
required?: boolean;
}
......@@ -46,6 +46,7 @@ const MultipleCheckbox: FC<Props> = ({
title={choice.label}
checked={value.includes(choice.value)}
onPress={() => handlePress(choice)}
textStyle={styles.textStyle}
/>
))}
</View>
......
......@@ -38,6 +38,7 @@ const MultipleChoice: FC<Props> = ({
title={choice.label}
checked={value === choice.value}
onPress={() => onChange(choice.value)}
textStyle={styles.textStyle}
/>
))}
</View>
......
import { StyleSheet } from 'react-native';
import { typography } from 'styles';
export const styles = StyleSheet.create({
spacing: {
......@@ -10,4 +11,10 @@ export const styles = StyleSheet.create({
red: {
color: 'red',
},
textStyle: {
fontWeight: '500',
color: 'black',
...typography.bodyLarge,
paddingRight: 14,
},
});
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