Intial commit
This commit is contained in:
54
src/components/OTPTextInput.tsx
Normal file
54
src/components/OTPTextInput.tsx
Normal file
@ -0,0 +1,54 @@
|
||||
import React, {forwardRef} from 'react';
|
||||
import {TextInput, StyleSheet, View} from 'react-native';
|
||||
import Colors from '../../assets/styles/Colors';
|
||||
import FontFamily from '../../assets/styles/FontFamily';
|
||||
|
||||
const OTPTextInput = forwardRef((props: any, ref: any) => {
|
||||
const {value, onChangeText, onKeyPress} = props;
|
||||
|
||||
const handleChange = (text: string) => {
|
||||
if (/^\d?$/.test(text)) {
|
||||
onChangeText(text);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.box}>
|
||||
<TextInput
|
||||
ref={ref}
|
||||
value={value}
|
||||
onChangeText={handleChange}
|
||||
onKeyPress={onKeyPress}
|
||||
style={styles.input}
|
||||
keyboardType="number-pad"
|
||||
maxLength={1}
|
||||
selectionColor={Colors.primary30.color}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
});
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
box: {
|
||||
width: 48,
|
||||
height: 48,
|
||||
borderRadius: 12,
|
||||
borderWidth: 1,
|
||||
borderColor: Colors.primary60.color,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
input: {
|
||||
fontSize: 16,
|
||||
color: 'black',
|
||||
textAlign: 'center',
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
...FontFamily.notoSansBold,
|
||||
includeFontPadding: false,
|
||||
},
|
||||
});
|
||||
|
||||
export default OTPTextInput;
|
217
src/components/TextInput.tsx
Normal file
217
src/components/TextInput.tsx
Normal file
@ -0,0 +1,217 @@
|
||||
import * as React from 'react';
|
||||
import {Platform, Pressable, StyleSheet, Text, View} from 'react-native';
|
||||
import {TextInput} from 'react-native-paper';
|
||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||
import Colors from '../../assets/styles/Colors';
|
||||
import FontFamily from '../../assets/styles/FontFamily';
|
||||
import DateTimePicker from '@react-native-community/datetimepicker';
|
||||
import {useState} from 'react';
|
||||
import {Dropdown} from 'react-native-element-dropdown';
|
||||
|
||||
interface TextInputComponentProps {
|
||||
title?: string;
|
||||
placeholder?: string;
|
||||
isPassword?: boolean;
|
||||
isRequired?: boolean;
|
||||
isDate?: boolean;
|
||||
isDropdown?: boolean;
|
||||
}
|
||||
|
||||
const genderData = [
|
||||
{label: 'Laki-Laki', value: '1'},
|
||||
{label: 'Perempuan', value: '2'},
|
||||
];
|
||||
|
||||
const TextInputComponent: React.FC<TextInputComponentProps> = ({
|
||||
title,
|
||||
placeholder,
|
||||
isPassword = false,
|
||||
isRequired = false,
|
||||
isDate = false,
|
||||
isDropdown = false,
|
||||
}) => {
|
||||
const [secureText, setSecureText] = useState(isPassword);
|
||||
const [selectedDate, setSelectedDate] = useState<Date | undefined>(undefined);
|
||||
const [formattedDate, setFormattedDate] = useState<string>('');
|
||||
const [showPicker, setShowPicker] = useState(false);
|
||||
const [genderValue, setGenderValue] = useState(null);
|
||||
|
||||
const renderGenderItem = (item: any) => {
|
||||
return (
|
||||
<View style={styles.genderItem}>
|
||||
<Text style={styles.genderTextItem}>{item.label}</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const handleDateChange = (event: any, date?: Date) => {
|
||||
if (event.type === 'dismissed') {
|
||||
setShowPicker(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (date) {
|
||||
setShowPicker(Platform.OS === 'ios');
|
||||
setSelectedDate(date);
|
||||
const formatted = `${String(date.getDate()).padStart(2, '0')}/${String(
|
||||
date.getMonth() + 1,
|
||||
).padStart(2, '0')}/${date.getFullYear()}`;
|
||||
setFormattedDate(formatted);
|
||||
}
|
||||
};
|
||||
|
||||
const renderInput = () => {
|
||||
if (isDropdown) {
|
||||
return (
|
||||
<View>
|
||||
{title && (
|
||||
<View style={styles.titleContainer}>
|
||||
<Text style={styles.title}>{title}</Text>
|
||||
{isRequired && <Text style={styles.required}>*</Text>}
|
||||
</View>
|
||||
)}
|
||||
<Dropdown
|
||||
style={styles.dropdown}
|
||||
placeholderStyle={styles.placeholderDropdownStyle}
|
||||
selectedTextStyle={styles.selectedTextStyle}
|
||||
iconStyle={styles.iconStyle}
|
||||
data={genderData}
|
||||
maxHeight={300}
|
||||
labelField="label"
|
||||
valueField="value"
|
||||
placeholder="Jenis Kelamin"
|
||||
value={genderValue}
|
||||
onChange={item => {
|
||||
setGenderValue(item.value);
|
||||
}}
|
||||
renderRightIcon={() => <Icon name="arrow-drop-down" size={20} />}
|
||||
renderItem={renderGenderItem}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (isDate) {
|
||||
return (
|
||||
<View>
|
||||
<View style={styles.titleContainer}>
|
||||
{title && <Text style={styles.title}>{title}</Text>}
|
||||
{isRequired && <Text style={styles.required}>*</Text>}
|
||||
</View>
|
||||
<Pressable onPress={() => setShowPicker(true)}>
|
||||
<TextInput
|
||||
mode="outlined"
|
||||
placeholder={placeholder}
|
||||
style={[styles.containerBackground, styles.placeholderText]}
|
||||
theme={{roundness: 12}}
|
||||
placeholderTextColor={Colors.primary60.color}
|
||||
editable={false}
|
||||
value={formattedDate}
|
||||
right={<TextInput.Icon icon="calendar" />}
|
||||
multiline={false}
|
||||
/>
|
||||
</Pressable>
|
||||
{showPicker && (
|
||||
<DateTimePicker
|
||||
value={selectedDate || new Date()}
|
||||
mode="date"
|
||||
display="calendar"
|
||||
onChange={handleDateChange}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
{title && (
|
||||
<View style={styles.titleContainer}>
|
||||
<Text style={styles.title}>{title}</Text>
|
||||
{isRequired && <Text style={styles.required}>*</Text>}
|
||||
</View>
|
||||
)}
|
||||
<TextInput
|
||||
mode="outlined"
|
||||
placeholder={placeholder}
|
||||
style={[styles.containerBackground, styles.placeholderText]}
|
||||
theme={{roundness: 12}}
|
||||
placeholderTextColor={Colors.primary60.color}
|
||||
activeOutlineColor={Colors.primary10.color}
|
||||
secureTextEntry={secureText}
|
||||
right={
|
||||
isPassword ? (
|
||||
<TextInput.Icon
|
||||
icon={secureText ? 'eye-off' : 'eye'}
|
||||
onPress={() => setSecureText(prev => !prev)}
|
||||
forceTextInputFocus={false}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
multiline={false}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
return <View>{renderInput()}</View>;
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
containerBackground: {
|
||||
backgroundColor: Colors.neutral100.color,
|
||||
marginTop: 8,
|
||||
},
|
||||
title: {
|
||||
...FontFamily.notoSansBold,
|
||||
fontSize: 12,
|
||||
},
|
||||
titleContainer: {
|
||||
flexDirection: 'row',
|
||||
gap: 5,
|
||||
},
|
||||
required: {
|
||||
...FontFamily.notoSansBold,
|
||||
fontSize: 12,
|
||||
color: Colors.indicatorRed.color,
|
||||
},
|
||||
placeholderText: {
|
||||
fontSize: 13,
|
||||
...FontFamily.notoSansRegular,
|
||||
},
|
||||
genderItem: {
|
||||
padding: 16,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
genderTextItem: {
|
||||
flex: 1,
|
||||
fontSize: 13,
|
||||
},
|
||||
dropdown: {
|
||||
marginTop: 8,
|
||||
backgroundColor: 'white',
|
||||
borderRadius: 12,
|
||||
paddingVertical: 16,
|
||||
paddingStart: 16,
|
||||
paddingEnd: 8,
|
||||
borderWidth: 1,
|
||||
borderColor: Colors.primary40.color,
|
||||
},
|
||||
placeholderDropdownStyle: {
|
||||
fontSize: 13,
|
||||
...FontFamily.notoSansRegular,
|
||||
color: Colors.primary60.color,
|
||||
},
|
||||
selectedTextStyle: {
|
||||
fontSize: 13,
|
||||
...FontFamily.notoSansRegular,
|
||||
},
|
||||
iconStyle: {
|
||||
width: 20,
|
||||
height: 20,
|
||||
},
|
||||
});
|
||||
|
||||
export default TextInputComponent;
|
74
src/navigation/RootStack.tsx
Normal file
74
src/navigation/RootStack.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
import React from 'react';
|
||||
import {createNativeStackNavigator} from '@react-navigation/native-stack';
|
||||
import LoginScreen from '../screens/login';
|
||||
import RegisterScreen from '../screens/register';
|
||||
import AccountVerificationScreen from '../screens/accountVerification';
|
||||
import HomeScreen from '../screens/home';
|
||||
import HistoryScreen from '../screens/history';
|
||||
import NotificationScreen from '../screens/notification';
|
||||
import ProfileScreen from '../screens/profile';
|
||||
import EditProfileScreen from '../screens/editProfile';
|
||||
import CloseAccountScreen from '../screens/closeAccount';
|
||||
import {RootStackParamList} from './type';
|
||||
import TermsAndConnditionsScreen from '../screens/termsAndConditions';
|
||||
|
||||
const Stack = createNativeStackNavigator<RootStackParamList>();
|
||||
|
||||
function RootStack() {
|
||||
return (
|
||||
<Stack.Navigator initialRouteName="Login">
|
||||
<Stack.Screen
|
||||
name="Login"
|
||||
component={LoginScreen}
|
||||
options={{headerShown: false}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="Register"
|
||||
component={RegisterScreen}
|
||||
options={{headerShown: false}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="AccountVerification"
|
||||
component={AccountVerificationScreen}
|
||||
options={{headerShown: false}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="TermsAndConditions"
|
||||
component={TermsAndConnditionsScreen}
|
||||
options={{headerShown: false}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="Home"
|
||||
component={HomeScreen}
|
||||
options={{headerShown: false}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="History"
|
||||
component={HistoryScreen}
|
||||
options={{headerShown: false}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="Notification"
|
||||
component={NotificationScreen}
|
||||
options={{headerShown: false}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="Profile"
|
||||
component={ProfileScreen}
|
||||
options={{headerShown: false}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="EditProfile"
|
||||
component={EditProfileScreen}
|
||||
options={{headerShown: false}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="CloseAccount"
|
||||
component={CloseAccountScreen}
|
||||
options={{headerShown: false}}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
);
|
||||
}
|
||||
|
||||
export default RootStack;
|
12
src/navigation/type.ts
Normal file
12
src/navigation/type.ts
Normal file
@ -0,0 +1,12 @@
|
||||
export type RootStackParamList = {
|
||||
Login: undefined;
|
||||
Home: undefined;
|
||||
Register: undefined;
|
||||
AccountVerification: undefined;
|
||||
TermsAndConditions: undefined;
|
||||
History: undefined;
|
||||
Notification: undefined;
|
||||
Profile: undefined;
|
||||
EditProfile: undefined;
|
||||
CloseAccount: undefined;
|
||||
};
|
92
src/screens/accountVerification/index.tsx
Normal file
92
src/screens/accountVerification/index.tsx
Normal file
@ -0,0 +1,92 @@
|
||||
import {useNavigation} from '@react-navigation/native';
|
||||
import {NativeStackNavigationProp} from '@react-navigation/native-stack';
|
||||
import React, {useEffect, useRef, useState} from 'react';
|
||||
import {Pressable, Text, View, TextInput, Keyboard} from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||
import {RootStackParamList} from '../../navigation/type';
|
||||
import styles from './styles';
|
||||
import {Button} from 'react-native-paper';
|
||||
import OTPTextInput from '../../components/OTPTextInput';
|
||||
|
||||
type AccountVerificationScreenNavigationProp = NativeStackNavigationProp<
|
||||
RootStackParamList,
|
||||
'AccountVerification'
|
||||
>;
|
||||
|
||||
function AccountVerificationScreen() {
|
||||
const navigation = useNavigation<AccountVerificationScreenNavigationProp>();
|
||||
|
||||
const [otp, setOtp] = useState(Array(6).fill(''));
|
||||
const inputRefs = useRef<TextInput[]>([]);
|
||||
|
||||
const handleChange = (text: string, index: number) => {
|
||||
const newOtp = [...otp];
|
||||
newOtp[index] = text;
|
||||
setOtp(newOtp);
|
||||
|
||||
if (text && index < 5) {
|
||||
inputRefs.current[index + 1]?.focus();
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyPress = (e: any, index: number) => {
|
||||
if (e.nativeEvent.key === 'Backspace') {
|
||||
const newOtp = [...otp];
|
||||
newOtp[index] = '';
|
||||
setOtp(newOtp);
|
||||
|
||||
if (index > 0) {
|
||||
inputRefs.current[index - 1]?.focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
Keyboard.dismiss();
|
||||
setTimeout(() => {
|
||||
inputRefs.current[0]?.focus();
|
||||
}, 100);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.appBarContainer}>
|
||||
<Pressable onPress={() => navigation.goBack()}>
|
||||
<Icon name="arrow-back" size={24} style={styles.arrowBackIcon} />
|
||||
</Pressable>
|
||||
</View>
|
||||
<View style={styles.accountVerificationTextWrapper}>
|
||||
<Text style={styles.accountVerificationTitleText}>Verifikasi Akun</Text>
|
||||
<Text style={styles.accountVerificationDescText}>
|
||||
Kode OTP telah dikirim ke email Anda. Mohon masukkan kode tersebut
|
||||
untuk verifikasi akun.
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.OTPTextInputContainer}>
|
||||
<View style={styles.OTPTextInputRowContainer}>
|
||||
{otp.map((digit: any, index: any) => (
|
||||
<OTPTextInput
|
||||
key={index}
|
||||
ref={(ref: any) => (inputRefs.current[index] = ref!)}
|
||||
value={digit}
|
||||
onChangeText={(text: string) => handleChange(text, index)}
|
||||
onKeyPress={(e: any) => handleKeyPress(e, index)}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
<Text style={styles.OTPTimeText}>
|
||||
Kirim ulang kode OTP dalam 10 detik
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={styles.sendOTPText}>Kirim Ulang Kode OTP</Text>
|
||||
<Button
|
||||
mode="contained"
|
||||
style={styles.accountVerificationButton}
|
||||
onPress={() => navigation.navigate('Home')}>
|
||||
Lanjut
|
||||
</Button>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default AccountVerificationScreen;
|
61
src/screens/accountVerification/styles.tsx
Normal file
61
src/screens/accountVerification/styles.tsx
Normal file
@ -0,0 +1,61 @@
|
||||
import {StyleSheet} from 'react-native';
|
||||
import FontFamily from '../../../assets/styles/FontFamily';
|
||||
import Colors from '../../../assets/styles/Colors';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
appBarContainer: {
|
||||
height: 64,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
arrowBackIcon: {
|
||||
marginLeft: 16,
|
||||
color: Colors.secondary30.color,
|
||||
},
|
||||
accountVerificationTextWrapper: {
|
||||
marginHorizontal: 16,
|
||||
},
|
||||
accountVerificationTitleText: {
|
||||
color: Colors.secondary30.color,
|
||||
...FontFamily.notoSansExtraBold,
|
||||
fontSize: 18,
|
||||
},
|
||||
accountVerificationDescText: {
|
||||
...FontFamily.notoSansRegular,
|
||||
fontSize: 12,
|
||||
textAlign: 'justify',
|
||||
},
|
||||
OTPTextInputContainer: {
|
||||
marginTop: 40,
|
||||
marginHorizontal: 16,
|
||||
},
|
||||
OTPTimeText: {
|
||||
marginTop: 16,
|
||||
textAlign: 'center',
|
||||
...FontFamily.notoSansRegular,
|
||||
color: Colors.primary50.color,
|
||||
},
|
||||
sendOTPText: {
|
||||
marginTop: 40,
|
||||
marginHorizontal: 16,
|
||||
textAlign: 'center',
|
||||
color: Colors.primary30.color,
|
||||
...FontFamily.notoSansSemiBold,
|
||||
fontSize: 12,
|
||||
},
|
||||
accountVerificationButton: {
|
||||
marginHorizontal: 16,
|
||||
marginTop: 16,
|
||||
backgroundColor: Colors.primary30.color,
|
||||
},
|
||||
OTPTextInputRowContainer: {
|
||||
flexDirection: 'row',
|
||||
gap: 6,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
export default styles;
|
21
src/screens/closeAccount/index.tsx
Normal file
21
src/screens/closeAccount/index.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import {StyleSheet, Text, View} from 'react-native';
|
||||
|
||||
function CloseAccountScreen() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text>Close Screen</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
});
|
||||
|
||||
export default CloseAccountScreen;
|
21
src/screens/editProfile/index.tsx
Normal file
21
src/screens/editProfile/index.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import {StyleSheet, Text, View} from 'react-native';
|
||||
|
||||
function EditProfileScreen() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text>Edit Screen</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
});
|
||||
|
||||
export default EditProfileScreen;
|
21
src/screens/history/index.tsx
Normal file
21
src/screens/history/index.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import {StyleSheet, Text, View} from 'react-native';
|
||||
|
||||
function HistoryScreen() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text>History Screen</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
});
|
||||
|
||||
export default HistoryScreen;
|
21
src/screens/home/index.tsx
Normal file
21
src/screens/home/index.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import {StyleSheet, Text, View} from 'react-native';
|
||||
|
||||
function HomeScreen() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text>Home Screen</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
});
|
||||
|
||||
export default HomeScreen;
|
80
src/screens/login/index.tsx
Normal file
80
src/screens/login/index.tsx
Normal file
@ -0,0 +1,80 @@
|
||||
import React from 'react';
|
||||
import {Image, Pressable, ScrollView, Text, View} from 'react-native';
|
||||
import styles from './styles';
|
||||
import {Button} from 'react-native-paper';
|
||||
import TextInputComponent from '../../components/TextInput';
|
||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||
import {useNavigation} from '@react-navigation/native';
|
||||
import {NativeStackNavigationProp} from '@react-navigation/native-stack';
|
||||
import {RootStackParamList} from '../../navigation/type';
|
||||
|
||||
type LoginScreenNavigationProp = NativeStackNavigationProp<
|
||||
RootStackParamList,
|
||||
'Login'
|
||||
>;
|
||||
|
||||
function LoginScreen() {
|
||||
const welcomeImage = require('../../../assets/images/welcomeImage.png');
|
||||
const navigation = useNavigation<LoginScreenNavigationProp>();
|
||||
|
||||
return (
|
||||
<ScrollView style={styles.container}>
|
||||
<Image source={welcomeImage} style={styles.welcomeImage} />
|
||||
<View style={styles.contentContainer}>
|
||||
<Text style={styles.welcomeText}>
|
||||
Selamat Datang di Aplikasi M-Paspor!
|
||||
</Text>
|
||||
<View style={styles.textInputContainer}>
|
||||
<TextInputComponent title="Email" placeholder="Masukkan Email" />
|
||||
<TextInputComponent
|
||||
title="Password"
|
||||
placeholder="Masukkan Password"
|
||||
isPassword={true}
|
||||
/>
|
||||
</View>
|
||||
<Text style={styles.forgotPasswordText}>Lupa kata sandi?</Text>
|
||||
<Button
|
||||
style={styles.loginButton}
|
||||
mode="contained"
|
||||
onPress={() => navigation.navigate('Home')}>
|
||||
Masuk
|
||||
</Button>
|
||||
<View style={styles.registerAccountContainer}>
|
||||
<Text style={styles.dontHaveAccountText}>Belum memiliki akun?</Text>
|
||||
<Pressable onPress={() => navigation.navigate('Register')}>
|
||||
<Text style={styles.registerAccountText}>Daftar akun</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
<Pressable
|
||||
onPress={() => navigation.navigate('TermsAndConditions')}
|
||||
style={({pressed}) => [{transform: [{scale: pressed ? 0.975 : 1}]}]}>
|
||||
<View style={styles.termsAndConditionsContainer}>
|
||||
<Icon
|
||||
name="person"
|
||||
size={20}
|
||||
color="#000"
|
||||
style={styles.personIcon}
|
||||
/>
|
||||
<View style={styles.termsAndConditionsTextContainer}>
|
||||
<Text style={styles.termsAndConditionsTitle}>
|
||||
Syarat & Ketentuan
|
||||
</Text>
|
||||
<Text style={styles.termsAndConditionsDescription}>
|
||||
Panduan pendaftaran dan informasi untuk mengajukan permohonan
|
||||
paspor
|
||||
</Text>
|
||||
</View>
|
||||
<Icon
|
||||
name="arrow-right"
|
||||
size={20}
|
||||
color="#000"
|
||||
style={styles.arrowRightIcon}
|
||||
/>
|
||||
</View>
|
||||
</Pressable>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
export default LoginScreen;
|
92
src/screens/login/styles.tsx
Normal file
92
src/screens/login/styles.tsx
Normal file
@ -0,0 +1,92 @@
|
||||
import {StyleSheet} from 'react-native';
|
||||
import Colors from '../../../assets/styles/Colors';
|
||||
import FontFamily from '../../../assets/styles/FontFamily';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: Colors.secondary70.color,
|
||||
flex: 1,
|
||||
},
|
||||
contentContainer: {
|
||||
alignSelf: 'stretch',
|
||||
backgroundColor: Colors.neutral100.color,
|
||||
marginHorizontal: 16,
|
||||
padding: 20,
|
||||
borderRadius: 20,
|
||||
},
|
||||
welcomeImage: {
|
||||
width: '100%',
|
||||
height: 275,
|
||||
resizeMode: 'stretch',
|
||||
},
|
||||
welcomeText: {
|
||||
color: Colors.secondary30.color,
|
||||
...FontFamily.notoSansExtraBold,
|
||||
fontSize: 24,
|
||||
marginBottom: 24,
|
||||
},
|
||||
loginButton: {
|
||||
marginTop: 24,
|
||||
backgroundColor: Colors.primary30.color,
|
||||
},
|
||||
registerAccountContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
gap: 5,
|
||||
marginTop: 16,
|
||||
},
|
||||
forgotPasswordText: {
|
||||
marginTop: 16,
|
||||
fontSize: 12,
|
||||
...FontFamily.notoSansSemiBold,
|
||||
color: Colors.secondary40.color,
|
||||
},
|
||||
textInputContainer: {
|
||||
gap: 16,
|
||||
},
|
||||
registerAccountText: {
|
||||
color: Colors.secondary40.color,
|
||||
fontSize: 12,
|
||||
...FontFamily.notoSansSemiBold,
|
||||
},
|
||||
dontHaveAccountText: {
|
||||
color: Colors.primary30.color,
|
||||
fontSize: 12,
|
||||
...FontFamily.notoSansRegular,
|
||||
},
|
||||
termsAndConditionsContainer: {
|
||||
alignSelf: 'stretch',
|
||||
backgroundColor: Colors.neutral100.color,
|
||||
flexDirection: 'row',
|
||||
marginHorizontal: 16,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
paddingVertical: 16,
|
||||
borderRadius: 20,
|
||||
marginTop: 16,
|
||||
marginBottom: 40,
|
||||
},
|
||||
termsAndConditionsTitle: {
|
||||
...FontFamily.notoSansMedium,
|
||||
fontSize: 14,
|
||||
color: Colors.primary30.color,
|
||||
},
|
||||
termsAndConditionsDescription: {
|
||||
...FontFamily.notoSansRegular,
|
||||
fontSize: 11,
|
||||
marginTop: 4,
|
||||
color: Colors.primary40.color,
|
||||
},
|
||||
termsAndConditionsTextContainer: {
|
||||
marginHorizontal: 10,
|
||||
flex: 1,
|
||||
},
|
||||
arrowRightIcon: {
|
||||
marginRight: 20,
|
||||
},
|
||||
personIcon: {
|
||||
marginLeft: 20,
|
||||
},
|
||||
});
|
||||
|
||||
export default styles;
|
21
src/screens/notification/index.tsx
Normal file
21
src/screens/notification/index.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import {StyleSheet, Text, View} from 'react-native';
|
||||
|
||||
function NotificationScreen() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text>Notification Screen</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
});
|
||||
|
||||
export default NotificationScreen;
|
21
src/screens/profile/index.tsx
Normal file
21
src/screens/profile/index.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import {StyleSheet, Text, View} from 'react-native';
|
||||
|
||||
function ProfileScreen() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text>Profile Screen</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
});
|
||||
|
||||
export default ProfileScreen;
|
126
src/screens/register/index.tsx
Normal file
126
src/screens/register/index.tsx
Normal file
@ -0,0 +1,126 @@
|
||||
import {NativeStackNavigationProp} from '@react-navigation/native-stack';
|
||||
import React, {useState} from 'react';
|
||||
import {Pressable, ScrollView, Text, View} from 'react-native';
|
||||
import {RootStackParamList} from '../../navigation/type';
|
||||
import {CommonActions, useNavigation} from '@react-navigation/native';
|
||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||
import styles from './styles';
|
||||
import TextInputComponent from '../../components/TextInput';
|
||||
import {Button, Checkbox} from 'react-native-paper';
|
||||
import Colors from '../../../assets/styles/Colors';
|
||||
|
||||
type RegisterScreenNavigationProp = NativeStackNavigationProp<
|
||||
RootStackParamList,
|
||||
'Register'
|
||||
>;
|
||||
|
||||
function RegisterScreen() {
|
||||
const navigation = useNavigation<RegisterScreenNavigationProp>();
|
||||
const [checked, setChecked] = useState(false);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.appBarContainer}>
|
||||
<Pressable onPress={() => navigation.goBack()}>
|
||||
<Icon name="arrow-back" size={24} style={styles.arrowBackIcon} />
|
||||
</Pressable>
|
||||
</View>
|
||||
<ScrollView>
|
||||
<View style={styles.registerTextWrapper}>
|
||||
<Text style={styles.accountRegistrationTitleText}>
|
||||
Pendaftaran Akun
|
||||
</Text>
|
||||
<Text style={styles.accountRegistrationDescText}>
|
||||
Buat akun untuk dapat masuk ke aplikasi
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.textInputContainer}>
|
||||
<TextInputComponent
|
||||
title="Nama Lengkap"
|
||||
placeholder="Masukkan Nama Lengkap"
|
||||
isRequired
|
||||
/>
|
||||
<View style={styles.textInputRowContainer}>
|
||||
<View style={styles.textInputFlex}>
|
||||
<TextInputComponent
|
||||
title="Tanggal Lahir"
|
||||
placeholder="DD/MM/YYYY"
|
||||
isRequired
|
||||
isDate
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.textInputFlex}>
|
||||
<TextInputComponent
|
||||
title="Jenis Kelamin"
|
||||
placeholder="Jenis Kelamin"
|
||||
isRequired
|
||||
isDropdown
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<TextInputComponent
|
||||
title="Email"
|
||||
placeholder="Masukkan Email"
|
||||
isRequired
|
||||
/>
|
||||
<TextInputComponent
|
||||
title="Nomor Telepon"
|
||||
placeholder="Masukkan Nomor Telepon"
|
||||
isRequired
|
||||
/>
|
||||
<TextInputComponent
|
||||
title="Kata Sandi"
|
||||
placeholder="Masukkan Kata Sandi"
|
||||
isPassword
|
||||
isRequired
|
||||
/>
|
||||
<TextInputComponent
|
||||
title="Ulang Kata Sandi"
|
||||
placeholder="Konfirmasi Kata Sandi"
|
||||
isPassword
|
||||
isRequired
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.acceptTermsAndConditionsContainer}>
|
||||
<Checkbox
|
||||
status={checked ? 'checked' : 'unchecked'}
|
||||
onPress={() => setChecked(!checked)}
|
||||
color={Colors.primary30.color}
|
||||
/>
|
||||
<Text style={styles.termsAndConditionsDesc}>
|
||||
Saya telah membaca dan menyetujui{' '}
|
||||
<Text
|
||||
style={styles.termsHighlight}
|
||||
onPress={() => navigation.navigate('TermsAndConditions')}>
|
||||
Syarat & Ketentuan
|
||||
</Text>{' '}
|
||||
yang berlaku
|
||||
</Text>
|
||||
</View>
|
||||
<Button
|
||||
style={styles.registerButton}
|
||||
mode="contained"
|
||||
onPress={() => navigation.navigate('AccountVerification')}>
|
||||
Daftar Akun
|
||||
</Button>
|
||||
<View style={styles.loginDescContainer}>
|
||||
<Text style={styles.loginDescText}>Sudah memiliki akun?</Text>
|
||||
<Text
|
||||
style={styles.loginHighlight}
|
||||
onPress={() =>
|
||||
navigation.dispatch(
|
||||
CommonActions.reset({
|
||||
index: 0,
|
||||
routes: [{name: 'Login'}],
|
||||
}),
|
||||
)
|
||||
}>
|
||||
Masuk
|
||||
</Text>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default RegisterScreen;
|
83
src/screens/register/styles.tsx
Normal file
83
src/screens/register/styles.tsx
Normal file
@ -0,0 +1,83 @@
|
||||
import {StyleSheet} from 'react-native';
|
||||
import Colors from '../../../assets/styles/Colors';
|
||||
import FontFamily from '../../../assets/styles/FontFamily';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
appBarContainer: {
|
||||
height: 64,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
arrowBackIcon: {
|
||||
marginLeft: 16,
|
||||
color: Colors.secondary30.color,
|
||||
},
|
||||
registerTextWrapper: {
|
||||
marginHorizontal: 16,
|
||||
},
|
||||
accountRegistrationTitleText: {
|
||||
color: Colors.secondary30.color,
|
||||
...FontFamily.notoSansExtraBold,
|
||||
fontSize: 18,
|
||||
},
|
||||
accountRegistrationDescText: {
|
||||
...FontFamily.notoSansRegular,
|
||||
fontSize: 12,
|
||||
},
|
||||
textInputContainer: {
|
||||
marginTop: 30,
|
||||
marginHorizontal: 16,
|
||||
gap: 16,
|
||||
},
|
||||
textInputRowContainer: {
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'row',
|
||||
gap: 12,
|
||||
},
|
||||
textInputFlex: {
|
||||
flex: 1,
|
||||
},
|
||||
acceptTermsAndConditionsContainer: {
|
||||
flexDirection: 'row',
|
||||
marginHorizontal: 16,
|
||||
marginTop: 20,
|
||||
},
|
||||
termsAndConditionsDesc: {
|
||||
marginLeft: 4,
|
||||
flex: 1,
|
||||
fontSize: 12,
|
||||
...FontFamily.notoSansRegular,
|
||||
textAlign: 'justify',
|
||||
},
|
||||
termsHighlight: {
|
||||
color: Colors.secondary40.color,
|
||||
...FontFamily.notoSansBold,
|
||||
fontSize: 12,
|
||||
},
|
||||
registerButton: {
|
||||
marginHorizontal: 16,
|
||||
marginTop: 24,
|
||||
backgroundColor: Colors.primary30.color,
|
||||
},
|
||||
loginDescContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
marginTop: 16,
|
||||
marginBottom: 20,
|
||||
gap: 5,
|
||||
},
|
||||
loginDescText: {
|
||||
...FontFamily.notoSansRegular,
|
||||
fontSize: 12,
|
||||
},
|
||||
loginHighlight: {
|
||||
color: Colors.secondary40.color,
|
||||
...FontFamily.notoSansBold,
|
||||
fontSize: 12,
|
||||
},
|
||||
});
|
||||
|
||||
export default styles;
|
21
src/screens/setPassword/index.tsx
Normal file
21
src/screens/setPassword/index.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import {StyleSheet, Text, View} from 'react-native';
|
||||
|
||||
function SetPasswordScreen() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text>Set Password Screen</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
});
|
||||
|
||||
export default SetPasswordScreen;
|
123
src/screens/termsAndConditions/index.tsx
Normal file
123
src/screens/termsAndConditions/index.tsx
Normal file
@ -0,0 +1,123 @@
|
||||
import React from 'react';
|
||||
import {Pressable, ScrollView, Text, View} from 'react-native';
|
||||
import styles from './styles';
|
||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||
import {RootStackParamList} from '../../navigation/type';
|
||||
import {NativeStackNavigationProp} from '@react-navigation/native-stack';
|
||||
import {useNavigation} from '@react-navigation/native';
|
||||
|
||||
type TermsAndConditionsScreenNavigationProp = NativeStackNavigationProp<
|
||||
RootStackParamList,
|
||||
'Register'
|
||||
>;
|
||||
|
||||
function TermsAndConnditionsScreen() {
|
||||
const navigation = useNavigation<TermsAndConditionsScreenNavigationProp>();
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.appBarContainer}>
|
||||
<Pressable onPress={() => navigation.goBack()}>
|
||||
<Icon name="arrow-back" size={24} style={styles.arrowBackIcon} />
|
||||
</Pressable>
|
||||
</View>
|
||||
<ScrollView>
|
||||
<View style={styles.termsAndConditionsTextWrapper}>
|
||||
<Text style={styles.termsAndConditionsTitleText}>
|
||||
Syarat & Ketentuan
|
||||
</Text>
|
||||
<Text style={styles.termsAndConditionsDescText}>
|
||||
Panduan pendaftaran dan informasi untuk mengajukan permohonan paspor
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.termsAndConditionsDetailWrapper}>
|
||||
<Text style={styles.heading}>
|
||||
1. Mohon persiapkan terlebih dahulu persyaratan permohonan paspor
|
||||
berikut:
|
||||
</Text>
|
||||
|
||||
<Text style={styles.subheading}>a. Permohonan paspor baru:</Text>
|
||||
<Text style={styles.bullet}>• KTP</Text>
|
||||
<Text style={styles.bullet}>• Kartu keluarga</Text>
|
||||
<Text style={styles.bullet}>
|
||||
• Akta kelahiran/ijazah/buku nikah/akte perkawinan/surat baptis
|
||||
</Text>
|
||||
<Text style={styles.bullet}>
|
||||
• Jika tidak ada, surat keterangan dari instansi
|
||||
</Text>
|
||||
|
||||
<Text style={styles.subheading}>
|
||||
b. Permohonan penggantian paspor:
|
||||
</Text>
|
||||
<Text style={styles.bullet}>• KTP</Text>
|
||||
<Text style={styles.bullet}>• Paspor lama</Text>
|
||||
|
||||
<Text style={styles.subheading}>
|
||||
c. Dokumen tambahan untuk paspor sesuai tujuan:
|
||||
</Text>
|
||||
<Text style={styles.bullet}>
|
||||
1. Haji/Umrah: Surat rekomendasi Kemenag
|
||||
</Text>
|
||||
<Text style={styles.bullet}>
|
||||
2. Belajar di luar negeri: Surat dari lembaga pendidikan
|
||||
</Text>
|
||||
<Text style={styles.bullet}>
|
||||
3. Magang/kerja: Surat rekomendasi Kemnaker
|
||||
</Text>
|
||||
|
||||
<Text style={styles.heading}>2. Pernyataan kesesuaian data</Text>
|
||||
<Text style={styles.paragraph}>
|
||||
Data yang ada pada seluruh dokumen harus sesuai dan dapat
|
||||
dipertanggungjawabkan kebenarannya.
|
||||
</Text>
|
||||
|
||||
<Text style={styles.heading}>
|
||||
3. Aplikasi ini hanya dapat melayani permohonan paspor baru dan
|
||||
penggantian
|
||||
</Text>
|
||||
<Text style={styles.paragraph}>
|
||||
Untuk penggantian karena rusak atau hilang, langsung datang ke
|
||||
Kantor Imigrasi.
|
||||
</Text>
|
||||
|
||||
<Text style={styles.heading}>4. Ketentuan pembayaran</Text>
|
||||
<Text style={styles.paragraph}>
|
||||
Pembayaran dilakukan terlebih dahulu sesuai pilihan jadwal dan
|
||||
kantor.
|
||||
</Text>
|
||||
|
||||
<Text style={styles.heading}>5. Perubahan jadwal</Text>
|
||||
<Text style={styles.paragraph}>
|
||||
Jadwal bisa diubah sebelum kedatangan, tapi tidak bisa dipastikan
|
||||
slot tersedia kembali.
|
||||
</Text>
|
||||
|
||||
<Text style={styles.heading}>
|
||||
6. Kehadiran dan pembatalan otomatis
|
||||
</Text>
|
||||
<Text style={styles.paragraph}>
|
||||
Tidak hadir tanpa konfirmasi akan dibatalkan otomatis dan tidak
|
||||
dapat digunakan kembali.
|
||||
</Text>
|
||||
|
||||
<Text style={styles.heading}>
|
||||
7. Permohonan paspor dapat ditolak dalam hal:
|
||||
</Text>
|
||||
<Text style={styles.bullet}>• Termasuk dalam daftar pencekalan</Text>
|
||||
<Text style={styles.bullet}>• Tersangkut kasus pidana</Text>
|
||||
<Text style={styles.bullet}>
|
||||
• Hal-hal lain yang dianggap oleh petugas
|
||||
</Text>
|
||||
|
||||
<Text style={styles.heading}>8. Tanggung jawab pemohon</Text>
|
||||
<Text style={styles.paragraph}>
|
||||
Kesalahan data menjadi tanggung jawab pemohon. Biaya yang telah
|
||||
dibayarkan tidak dapat dikembalikan.
|
||||
</Text>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default TermsAndConnditionsScreen;
|
64
src/screens/termsAndConditions/styles.tsx
Normal file
64
src/screens/termsAndConditions/styles.tsx
Normal file
@ -0,0 +1,64 @@
|
||||
import {StyleSheet} from 'react-native';
|
||||
import Colors from '../../../assets/styles/Colors';
|
||||
import FontFamily from '../../../assets/styles/FontFamily';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
appBarContainer: {
|
||||
height: 64,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
arrowBackIcon: {
|
||||
marginLeft: 16,
|
||||
color: Colors.secondary30.color,
|
||||
},
|
||||
termsAndConditionsTextWrapper: {
|
||||
marginHorizontal: 16,
|
||||
},
|
||||
termsAndConditionsTitleText: {
|
||||
color: Colors.secondary30.color,
|
||||
...FontFamily.notoSansExtraBold,
|
||||
fontSize: 18,
|
||||
},
|
||||
termsAndConditionsDescText: {
|
||||
...FontFamily.notoSansRegular,
|
||||
fontSize: 12,
|
||||
textAlign: 'justify',
|
||||
},
|
||||
termsAndConditionsDetailWrapper: {
|
||||
marginHorizontal: 16,
|
||||
marginVertical: 32,
|
||||
},
|
||||
content: {
|
||||
padding: 16,
|
||||
paddingBottom: 32,
|
||||
},
|
||||
heading: {
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 6,
|
||||
color: '#222',
|
||||
},
|
||||
subheading: {
|
||||
fontSize: 15,
|
||||
fontWeight: '600',
|
||||
marginTop: 10,
|
||||
color: '#444',
|
||||
},
|
||||
bullet: {
|
||||
fontSize: 14,
|
||||
marginLeft: 12,
|
||||
marginBottom: 4,
|
||||
color: '#333',
|
||||
},
|
||||
paragraph: {
|
||||
fontSize: 14,
|
||||
marginBottom: 8,
|
||||
color: '#333',
|
||||
},
|
||||
});
|
||||
|
||||
export default styles;
|
Reference in New Issue
Block a user