Fakultas Ilmu Komputer UI

Commit 6ac57798 authored by Jahns's avatar Jahns
Browse files

Merge branch 'jahns/edit-profile-form' into 'pbi-15-edit-profile'

Jahns/edit profile form

See merge request !7
parents e784aca0 31af6eba
Pipeline #81880 passed with stage
in 14 minutes and 31 seconds
......@@ -14,6 +14,9 @@ import {
ContactInvestigationFormStep2,
ContactInvestigationFormStep3,
ContactInvestigationFormStep4,
EditProfileForm,
ProfilePage,
ForgetPasswordPage,
MonitoringSelection,
MonitoringMedicalReference,
MonitoringFormFinishPage,
......@@ -28,7 +31,6 @@ import initialCacheState, {
} from 'contexts/AppContext/cache';
import { useMainService } from 'services'
import { Box, Text, Button, Gap } from 'components';
import ForgetPasswordPage from 'scenes/ForgetPasswordPage';
const StyledApp = styled.SafeAreaView`
height: 100%;
......@@ -287,6 +289,8 @@ const App = () => {
<Stack.Screen name="login" component={LoginPage} />
<Stack.Screen name="forget-password" component={ForgetPasswordPage} />
<Stack.Screen name="home" component={Home} />
<Stack.Screen name="profile" component={ProfilePage} />
<Stack.Screen name="edit-profile" component={EditProfileForm} />
<Stack.Screen
name="contact-investigation-form/1"
component={ContactInvestigationFormStep1}
......
......@@ -163,7 +163,7 @@ exports[`DatePicker tests renders correctly 1`] = `
]
}
>
17/5/2021
6/6/2021
</Text>
</View>
<View
......
import {AppContext} from 'contexts';
import { AppContext } from 'contexts';
import { useNavigation } from '@react-navigation/native';
import React, {useContext} from 'react';
import React, { useContext } from 'react';
import styled from 'styled-components/native';
import {Image} from 'react-native';
import {Button, Text, Box} from 'components';
import { Image } from 'react-native';
import { Button, Text, Box } from 'components';
interface HeaderProps {
isHome?: boolean;
......@@ -25,19 +25,24 @@ const Header = ({
<BackgroundMain>
<HeaderMain>
<ChildrenContent>
{isHome? (
{isHome ? (
<>
<Box
grow={1}
/>
<FlexControl>
<FlexControl >
<Image
style={{borderRadius: 100, width: 80, height: 80}}
style={{ borderRadius: 100, width: 80, height: 80 }}
source={require('assets/icons/user-placeholder.png')}
/>
<TextView>
<Text isBold type={Text.StyleType.Larger} >{`Halo, ${greetingName} !`}</Text>
<Text type={Text.StyleType.Medium} >Semangat berantas TB</Text>
<TouchableText
onPress={() => global.isAuthenticated ? navigation.navigate("profile") : navigation.navigate("login")}
>
<Text>Lihat Profile</Text>
</TouchableText>
</TextView>
</FlexControl>
</>
......@@ -51,7 +56,7 @@ const Header = ({
<Button
width="30px"
type={Button.Type.Outline}
onPress={() => global.isAuthenticated? navigation.navigate("home"): navigation.navigate("login")}
onPress={() => global.isAuthenticated ? navigation.navigate("home") : navigation.navigate("login")}
>
Beranda
</Button>
......@@ -126,4 +131,10 @@ const HeaderSecondary = styled.SafeAreaView`
border-top-left-radius: 80px;
`;
const TouchableText = styled.TouchableOpacity`
height: auto;
width: auto;
border-width: 0px;
`;
export default Header;
import 'react-native';
import React from 'react';
import axios from 'axios';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import ReactTestRenderer, { act } from 'react-test-renderer';
import EditProfileForm from '.';
import { AppContext } from 'contexts';
import { useMainService } from 'services';
const Stack = createStackNavigator();
const testProps = {
services: {
main: useMainService('dummyToken'),
},
user: {
name: "test name",
username: "testusername",
email: "test@email.com",
phone_number: "0123456789",
district: "Tapos",
sub_district: "Sukatani",
},
setUser: (data: any) => {
testProps.user = data;
},
cache: {},
}
jest.mock('axios');
jest.useFakeTimers();
const mockedAxios = axios as jest.Mocked<typeof axios>;
describe("EditProfileForm", () => {
it('renders correctly', () => {
const instance = ReactTestRenderer.create(
<AppContext.Provider value={testProps}>
<NavigationContainer>
<Stack.Navigator
screenOptions={{
header: () => <></>,
}}>
<Stack.Screen name="edit-profile" component={EditProfileForm} />
</Stack.Navigator>
</NavigationContainer>,
</AppContext.Provider>
);
expect(instance.toJSON()).toMatchSnapshot();
});
it('success with insert fields with valid values', async () => {
const formPage = ReactTestRenderer.create(
<AppContext.Provider value={testProps}>
<NavigationContainer>
<Stack.Navigator
screenOptions={{
// Empty header
header: () => <></>,
}}>
<Stack.Screen name="edit-profile" component={EditProfileForm} />
</Stack.Navigator>
</NavigationContainer>
</AppContext.Provider>
);
expect(formPage).toBeTruthy()
const nameField = formPage.root.find(elem => elem.props.id === "name")
expect(nameField).toBeTruthy()
const usernameField = formPage.root.find(elem => elem.props.id === "username")
expect(usernameField).toBeTruthy()
const passwordField = formPage.root.find(elem => elem.props.id === "password")
expect(passwordField).toBeTruthy()
const confirmPasswordField = formPage.root.find(elem => elem.props.id === "confirm_password")
expect(confirmPasswordField).toBeTruthy()
const emailField = formPage.root.find(elem => elem.props.id === "email")
expect(emailField).toBeTruthy()
const phoneField = formPage.root.find(elem => elem.props.id === "phone_number")
expect(phoneField).toBeTruthy()
const districtField = formPage.root.find(elem => elem.props.id === "district")
expect(districtField).toBeTruthy()
const sub_districtField = formPage.root.find(elem => elem.props.id === "sub_district")
expect(sub_districtField).toBeTruthy()
// Set value
act(() => {
if (nameField && usernameField && passwordField && confirmPasswordField && emailField && phoneField && districtField && sub_districtField) {
nameField.props.updateValue("your name")
usernameField.props.updateValue("username")
passwordField.props.updateValue("asdQWE123")
confirmPasswordField.props.updateValue("asdQWE123")
emailField.props.updateValue("email@gmail.com")
phoneField.props.updateValue("081802769312")
districtField.props.updateValue("districtku")
sub_districtField.props.updateValue("subdistrictku")
}
})
expect(formPage.toJSON()).toMatchSnapshot()
mockedAxios.request.mockImplementationOnce(
() => new Promise(resolve => {
resolve({
status: 200,
data: {
"message": "success edit profile",
},
});
})
);
const submit = formPage.root.find(elem => elem.props.id === "submit")
await act(async () => {
submit.props.onPress()
})
});
})
\ No newline at end of file
import React, { useState, useContext, useEffect } from 'react';
import styled from 'styled-components/native';
import { ActivityIndicator, ScrollView, View, Modal, Image } from 'react-native';
import { Header, Field, Button, Text } from '../../components';
import error_img from '../OfficerSignupForm/img/folder.png';
import { useFormState } from 'helpers';
import { KECAMATAN_VALUES, KELURAHAN_VALUES } from '../OfficerSignupForm/constants';
import { useNavigation } from '@react-navigation/native';
import { AppContext } from 'contexts';
import translateError from '../OfficerSignupForm/utilities'
const EditProfileForm = () => {
const navigation = useNavigation();
const global = useContext(AppContext);
const headingSize = 0.028 * global.vh;
const imageSize = 0.15 * global.vh;
const [form, setField] = useFormState({
name: { type: 'text' },
username: { type: 'any' },
password: { type: 'password' },
confirm_password: { type: 'password' },
email: { type: 'email' },
phone_number: { type: 'phone' },
district: { type: 'any' },
sub_district: { type: 'any' },
});
const [serverHasError, setServerHasError] = useState(false);
const [messageErrors, setMessageErrors] = useState({
name: '',
username: '',
password: '',
confirm_password: '',
email: '',
phone_number: '',
district: '',
sub_district: '',
})
const [modalVisible, setModalVisible] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const initFieldValue = () => {
setField('name', global.user.name);
setField('username', global.user.username);
setField('email', global.user.email);
setField('phone_number', global.user.phone_number);
setField('district', global.user.district);
setField('sub_district', global.user.sub_district);
}
useEffect(initFieldValue, []);
return (
<View>
<ScrollView>
<Header headerText="Edit Profile" />
<ContainerContent>
<Modal animationType="fade" transparent={true} visible={isLoading}>
<ModalContainer>
<ActivityIndicator
animating={isLoading}
size="large"
color="#fff"
/>
</ModalContainer>
</Modal>
<Modal
id="errorModal"
animationType="fade"
transparent={true}
visible={modalVisible}
onDismiss={() => setServerHasError(false)}>
<ModalContainer>
<ModalInner>
<Image
style={{ width: imageSize, height: imageSize }}
source={error_img}
/>
<HeadingField>
{serverHasError ? (
<>
<Text isBold={true} fontSize={headingSize}>
Unknown Server Error
</Text>
</>
) : (
<>
<Text isBold={true} fontSize={headingSize}>
Terjadi kesalahan, periksa kembali data anda.
</Text>
</>
)}
</HeadingField>
<Button id="modalBack" type={2} onPress={() => setModalVisible(false)}>
Kembali
</Button>
</ModalInner>
</ModalContainer>
</Modal>
<InputField>
<Field
id="name"
name="Nama"
placeholder="Nama Lengkap Anda"
isRequired={true}
information={messageErrors.name}
value={form.fields.name.value}
updateValue={val => setField('name', val)}
/>
</InputField>
<InputField>
<Field
id="username"
name="Username"
placeholder="Username Anda"
isRequired={true}
information={messageErrors.username}
value={form.fields.username.value}
updateValue={val => setField('username', val)}
/>
</InputField>
<InputField>
<Field
id="password"
name="Password"
placeholder="Password Anda"
isRequired={true}
shouldSecure={true}
information={messageErrors.password}
value={form.fields.password.value}
updateValue={val => setField('password', val)}
/>
</InputField>
<InputField>
<Field
id="confirm_password"
name="Konfirmasi Password"
placeholder="Konfirmasi Password Anda"
isRequired={true}
shouldSecure={true}
information={messageErrors.confirm_password}
value={form.fields.confirm_password.value}
updateValue={val => setField('confirm_password', val)}
/>
</InputField>
<InputField>
<Field
id="email"
name="Email"
placeholder="Email Anda"
isRequired={true}
information={messageErrors.email}
value={form.fields.email.value}
updateValue={val => setField('email', val)}
/>
</InputField>
<InputField>
<Field
id="phone_number"
name="Nomor Handphone"
placeholder="No HP Anda"
isRequired={true}
information={messageErrors.phone_number}
value={form.fields.phone_number.value}
updateValue={val => setField('phone_number', val)}
/>
</InputField>
<InputField>
<Field
id="district"
type={3}
name="Kecamatan"
placeholder="Pilih Kecamatan"
isRequired={true}
information={messageErrors.district}
value={form.fields.district.value || KECAMATAN_VALUES[0].value}
updateValue={val => setField('district', val)}
values={KECAMATAN_VALUES}
/>
</InputField>
<InputField>
<Field
id="sub_district"
type={3}
name="Kelurahan"
placeholder="Pilih Kelurahan"
isRequired={true}
information={messageErrors.sub_district}
value={form.fields.sub_district.value || KELURAHAN_VALUES[KECAMATAN_VALUES[0].value][0].value}
updateValue={val => setField('sub_district', val)}
values={(form.fields.district.value) ? KELURAHAN_VALUES[form.fields.district.value] : KELURAHAN_VALUES[KECAMATAN_VALUES[0].value]}
/>
</InputField>
<Button
id="submit"
type={1}
onPress={async () => {
setIsLoading(true)
const response = await global.services.main.editProfile({
name: form.fields.name.value,
username: form.fields.username.value.replace(/\s+$/, ''),
password: form.fields.password.value,
confirm_password: form.fields.confirm_password.value,
email: form.fields.email.value,
phone_number: form.fields.phone_number.value,
district: form.fields.district.value,
sub_district: form.fields.sub_district.value
})
if (response.status === 200) {
setIsLoading(false);
const meRes = await global.services.main.me()
if (meRes.status === 200) {
global.setUser(meRes.data)
} else {
global.setToken('')
}
navigation.reset({
index: 0,
routes: [{ name: "profile" }],
});
} else if (response.status === 400) {
const errors = {
name: response.data.name || [],
username: response.data.username || [],
password: response.data.password || [],
confirm_password: response.data.confirm_password || [],
email: response.data.email || [],
phone_number: response.data.phone_number || [],
district: response.data.district || [],
sub_district: response.data.sub_district || [],
}
if (response.data.non_field_errors) {
switch (response.data.non_field_errors[0]) {
case "Inconsistent password and confirmation":
errors.confirm_password = [
...errors.confirm_password,
"Password dan konfirmasinya tidak konsisten"
]
break;
case "Inconsistent district and sub district value":
errors.district = [
...errors.district,
"Kecamatan dan kelurahan tidak konsisten"
]
break;
default:
break;
}
}
setMessageErrors({
name: translateError(errors.name[0] || ""),
username: translateError(errors.username[0] || ""),
password: translateError(errors.password[0] || ""),
confirm_password: translateError(errors.confirm_password[0] || ""),
email: translateError(errors.email[0] || ""),
phone_number: translateError(errors.phone_number[0] || ""),
district: translateError(errors.district[0] || ""),
sub_district: translateError(errors.sub_district[0] || ""),
})
} else if (response.status === 500) {
if (response.data.includes("IntegrityError")) {
setServerHasError(false);
} else {
setServerHasError(true);
}
setModalVisible(true);
}
setIsLoading(false);
}}>
Edit Profile
</Button>
</ContainerContent>
</ScrollView>
</View>
);
};
const ContainerContent = styled.View`
padding: 5%;
background-color: white;
`;
const InputField = styled.View`
margin-bottom: 5%;
`;
const ModalContainer = styled.View`
flex: 1;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #00000080;
`;
const HeadingField = styled.View`
margin: 10% 0;
align-items: center;
`;
const ModalInner = styled.View`
align-items: center;
background-color: #fff;
padding: 15%;
height: ${props => props.theme.vh * 0.55};
`;
export default EditProfileForm;
......@@ -320,6 +320,43 @@ exports[`renders correctly 1`] = `
>
Semangat berantas TB
</Text>
<View
accessible={true}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"borderWidth": 0,
"height": "auto",
"opacity": 1,
"width": "auto",
}
}
>
<Text
fontSize="16px"
fontWeight="Regular"
style={
Array [
Object {
"color": "black",
"fontFamily": "Dosis-Regular",
"fontSize": 16,
"textAlign": "left",
"width": "auto",
},
]
}
>
Lihat Profile