@boneframework/native-components 1.0.2 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/Screens.ts +4 -0
- package/components/ActivityIndicator.js +48 -0
- package/components/Animation.js +21 -0
- package/components/ApiInterceptor.js +104 -0
- package/components/Button.js +40 -0
- package/components/Card.js +56 -0
- package/components/CategoryPickerItem.js +31 -0
- package/components/DateTimePicker.js +12 -0
- package/components/Icon.js +28 -0
- package/components/Image.js +61 -0
- package/components/ImageInput.tsx +97 -0
- package/components/ImageInputList.tsx +34 -0
- package/components/ListItemDeleteAction.tsx +30 -0
- package/components/ListItemFlipswitch.tsx +60 -0
- package/components/ListItemSeparator.tsx +23 -0
- package/components/ListItemSwipable.tsx +64 -0
- package/components/OfflineNotice.tsx +36 -0
- package/components/Picker.tsx +87 -0
- package/components/PickerItem.tsx +20 -0
- package/components/PickerItemComponent.tsx +14 -0
- package/components/RoundIconButton.tsx +36 -0
- package/components/Screen.tsx +26 -0
- package/components/Text.tsx +15 -0
- package/components/TextInput.tsx +39 -0
- package/components/forms/ErrorMessage.js +20 -0
- package/components/forms/Form.js +21 -0
- package/components/forms/FormDateTimePicker.js +31 -0
- package/components/forms/FormField.js +26 -0
- package/components/forms/FormImagePicker.js +30 -0
- package/components/forms/FormPicker.js +30 -0
- package/components/forms/SubmitButton.js +14 -0
- package/components/forms/index.js +7 -0
- package/hooks/useApi.js +20 -0
- package/package.json +4 -2
- package/screens/RegisterScreen.tsx +87 -0
- package/screens/WelcomeScreen.tsx +50 -0
- package/src/Screens.ts +0 -1
- /package/{src/Bone.ts → Bone.ts} +0 -0
- /package/{src/Components.ts → Components.ts} +0 -0
- /package/{src/Contexts.ts → Contexts.ts} +0 -0
- /package/{src/Hooks.ts → Hooks.ts} +0 -0
- /package/{src/Utilities.ts → Utilities.ts} +0 -0
- /package/{src/api → api}/client.js +0 -0
- /package/{src/api → api}/expoPushTokens.js +0 -0
- /package/{src/api → api}/notifications.js +0 -0
- /package/{src/api → api}/ping.js +0 -0
- /package/{src/api → api}/users.js +0 -0
- /package/{src/components → components}/SessionProvider.tsx +0 -0
- /package/{src/contexts → contexts}/auth.js +0 -0
- /package/{src/hooks → hooks}/useAuth.js +0 -0
- /package/{src/utilities → utilities}/authStorage.js +0 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {StyleSheet, TouchableHighlight, View} from "react-native";
|
|
3
|
+
import Swipeable from 'react-native-gesture-handler/Swipeable';
|
|
4
|
+
|
|
5
|
+
import Image from '../components/Image'
|
|
6
|
+
import Text from '../components/Text'
|
|
7
|
+
import colors from '../config/colors'
|
|
8
|
+
import {MaterialCommunityIcons} from "@expo/vector-icons";
|
|
9
|
+
import useStyle from "../hooks/useStyle";
|
|
10
|
+
|
|
11
|
+
function ListItemSwipable({title, subtitle, image, IconComponent, onPress, renderRightActions, displayCheverons}) {
|
|
12
|
+
const style = useStyle();
|
|
13
|
+
|
|
14
|
+
if (!title && subtitle) {
|
|
15
|
+
title = subtitle;
|
|
16
|
+
subtitle = null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const styles = StyleSheet.create({
|
|
20
|
+
container: {
|
|
21
|
+
flexDirection: 'row',
|
|
22
|
+
alignItems: 'center',
|
|
23
|
+
padding: 15,
|
|
24
|
+
backgroundColor: style.box.backgroundColor
|
|
25
|
+
},
|
|
26
|
+
detailsContainer: {
|
|
27
|
+
flex: 1,
|
|
28
|
+
marginLeft: 10,
|
|
29
|
+
justifyContent: "center"
|
|
30
|
+
},
|
|
31
|
+
image: {
|
|
32
|
+
width: 70,
|
|
33
|
+
height: 70,
|
|
34
|
+
borderRadius: 35,
|
|
35
|
+
},
|
|
36
|
+
title: {
|
|
37
|
+
color: style.text.color,
|
|
38
|
+
fontWeight: "500"
|
|
39
|
+
},
|
|
40
|
+
subtitle: {
|
|
41
|
+
color: colors.medium
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<Swipeable renderRightActions={renderRightActions}>
|
|
47
|
+
<TouchableHighlight underlayColor={colors.light} onPress={onPress} >
|
|
48
|
+
<View style={styles.container}>
|
|
49
|
+
{ IconComponent }
|
|
50
|
+
{ image && <Image style={styles.image} source={image}></Image> }
|
|
51
|
+
<View style={styles.detailsContainer}>
|
|
52
|
+
<Text style={styles.title} numberOfLines={1}>
|
|
53
|
+
{ title }
|
|
54
|
+
</Text>
|
|
55
|
+
{subtitle && <Text numberOfLines={2} style={styles.subtitle}>{subtitle}</Text> }
|
|
56
|
+
</View>
|
|
57
|
+
{ displayCheverons && <MaterialCommunityIcons name={'chevron-right'} size={25} color={colors.medium}/> }
|
|
58
|
+
</View>
|
|
59
|
+
</TouchableHighlight>
|
|
60
|
+
</Swipeable>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export default ListItemSwipable;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {StyleSheet, View} from "react-native";
|
|
3
|
+
import {useNetInfo} from "@react-native-community/netinfo";
|
|
4
|
+
|
|
5
|
+
import colors from "../config/colors";
|
|
6
|
+
import Text from "./Text";
|
|
7
|
+
|
|
8
|
+
function OfflineNotice(props) {
|
|
9
|
+
const netInfo = useNetInfo();
|
|
10
|
+
|
|
11
|
+
if (netInfo.type !== 'unkown' && netInfo.isInternetReachable === false) {
|
|
12
|
+
return(
|
|
13
|
+
<View style={styles.container}>
|
|
14
|
+
<Text style={styles.text}>No Internet connection</Text>
|
|
15
|
+
</View>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const styles = StyleSheet.create({
|
|
23
|
+
container: {
|
|
24
|
+
backgroundColor: colors.primary,
|
|
25
|
+
color: colors.white,
|
|
26
|
+
alignItems: "center",
|
|
27
|
+
justifyContent: "center",
|
|
28
|
+
padding: 10,
|
|
29
|
+
paddingTop: 20
|
|
30
|
+
},
|
|
31
|
+
text: {
|
|
32
|
+
color: colors.white,
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
export default OfflineNotice;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import React, {useState} from 'react';
|
|
2
|
+
import {FlatList, Modal, StyleSheet, TextInput, TouchableWithoutFeedback, View} from "react-native";
|
|
3
|
+
import {MaterialCommunityIcons} from "@expo/vector-icons";
|
|
4
|
+
|
|
5
|
+
import Button from './Button'
|
|
6
|
+
import Text from './Text'
|
|
7
|
+
import PickerItem from './PickerItem'
|
|
8
|
+
import PickerItemComponent from './PickerItemComponent'
|
|
9
|
+
import Screen from './Screen'
|
|
10
|
+
import useStyle from "../hooks/useStyle";
|
|
11
|
+
|
|
12
|
+
function Picker({
|
|
13
|
+
icon,
|
|
14
|
+
items,
|
|
15
|
+
onSelectItem,
|
|
16
|
+
PickerItemComponent = PickerItem,
|
|
17
|
+
placeholder,
|
|
18
|
+
selectedItem,
|
|
19
|
+
width = '100%',
|
|
20
|
+
numColumns = 1
|
|
21
|
+
}) {
|
|
22
|
+
const [modalVisible, setModalVisible] = useState(false);
|
|
23
|
+
const defaultStyles = useStyle();
|
|
24
|
+
|
|
25
|
+
const style = StyleSheet.create({
|
|
26
|
+
container: {
|
|
27
|
+
backgroundColor: defaultStyles.formInput.backgroundColor,
|
|
28
|
+
borderRadius: 25,
|
|
29
|
+
flexDirection: 'row',
|
|
30
|
+
width: '100%',
|
|
31
|
+
padding: 15,
|
|
32
|
+
marginVertical: 10,
|
|
33
|
+
},
|
|
34
|
+
icon: {
|
|
35
|
+
marginRight: 10
|
|
36
|
+
},
|
|
37
|
+
modal: {
|
|
38
|
+
flex: 1,
|
|
39
|
+
padding: 15,
|
|
40
|
+
backgroundColor: defaultStyles.backgroundColor,
|
|
41
|
+
color: defaultStyles.text.color
|
|
42
|
+
},
|
|
43
|
+
placeholder: {
|
|
44
|
+
flex: 1,
|
|
45
|
+
color: defaultStyles.colors.medium
|
|
46
|
+
},
|
|
47
|
+
text: {
|
|
48
|
+
flex: 1
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<View>
|
|
54
|
+
<TouchableWithoutFeedback onPress={() => setModalVisible(true)}>
|
|
55
|
+
<View style={[style.container, {width: width}]}>
|
|
56
|
+
{icon && <MaterialCommunityIcons name={icon} size={25} color={defaultStyles.colors.medium} style={style.icon} />}
|
|
57
|
+
<Text style={selectedItem ? [defaultStyles.text, style.text] : [defaultStyles.text, style.placeholder] } >
|
|
58
|
+
{ selectedItem ? selectedItem.label : placeholder }
|
|
59
|
+
</Text>
|
|
60
|
+
<MaterialCommunityIcons name="chevron-down" size={25} color={defaultStyles.colors.medium} />
|
|
61
|
+
</View>
|
|
62
|
+
</TouchableWithoutFeedback>
|
|
63
|
+
|
|
64
|
+
<Modal visible={modalVisible} animationType="slide">
|
|
65
|
+
<Screen style={style.modal}>
|
|
66
|
+
<Button title="Close" onPress={() => setModalVisible(false)}/>
|
|
67
|
+
<FlatList
|
|
68
|
+
data={items}
|
|
69
|
+
keyExtractor={item => item.value}
|
|
70
|
+
numColumns={numColumns}
|
|
71
|
+
renderItem={({item}) =>
|
|
72
|
+
<PickerItemComponent
|
|
73
|
+
item={item}
|
|
74
|
+
label={item.label}
|
|
75
|
+
onPress={() => {
|
|
76
|
+
setModalVisible(false);
|
|
77
|
+
onSelectItem(item);
|
|
78
|
+
}}
|
|
79
|
+
/>
|
|
80
|
+
} />
|
|
81
|
+
</Screen>
|
|
82
|
+
</Modal>
|
|
83
|
+
</View>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export default Picker;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {StyleSheet, TouchableOpacity, View} from "react-native";
|
|
3
|
+
|
|
4
|
+
import Text from './Text';
|
|
5
|
+
|
|
6
|
+
function PickerItem({ item, onPress }) {
|
|
7
|
+
return (
|
|
8
|
+
<TouchableOpacity onPress={onPress}>
|
|
9
|
+
<Text style={styles.text}>{item.label}</Text>
|
|
10
|
+
</TouchableOpacity>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const styles = StyleSheet.create({
|
|
15
|
+
text: {
|
|
16
|
+
padding: 20
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export default PickerItem;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {StyleSheet, View} from "react-native";
|
|
3
|
+
|
|
4
|
+
function PickerItemComponent({item, onPress}) {
|
|
5
|
+
return <View style={styles.container}></View>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const styles = StyleSheet.create({
|
|
9
|
+
container: {
|
|
10
|
+
|
|
11
|
+
}
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
export default PickerItemComponent;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {StyleSheet, TouchableOpacity, View} from "react-native";
|
|
3
|
+
import {MaterialCommunityIcons} from "@expo/vector-icons";
|
|
4
|
+
|
|
5
|
+
import colors from '../config/colors'
|
|
6
|
+
|
|
7
|
+
function RoundIconButton({ icon, onPress = () => {}, style }) {
|
|
8
|
+
const combinedStyle = { ...styles.container, ...style };
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<TouchableOpacity onPress={ onPress } >
|
|
12
|
+
<View style={combinedStyle}>
|
|
13
|
+
<MaterialCommunityIcons style={styles.icon} name={icon} size={40}/>
|
|
14
|
+
</View>
|
|
15
|
+
</TouchableOpacity>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const styles = StyleSheet.create({
|
|
20
|
+
container: {
|
|
21
|
+
alignItems: 'center',
|
|
22
|
+
justifyContent: 'center',
|
|
23
|
+
backgroundColor: colors.primary,
|
|
24
|
+
borderColor: colors.white,
|
|
25
|
+
borderWidth: 5,
|
|
26
|
+
borderRadius: 40,
|
|
27
|
+
height: 80,
|
|
28
|
+
width: 80,
|
|
29
|
+
bottom: 25
|
|
30
|
+
},
|
|
31
|
+
icon: {
|
|
32
|
+
color: colors.white
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
export default RoundIconButton;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {SafeAreaView, StyleSheet, View} from "react-native";
|
|
3
|
+
import Constants from "expo-constants";
|
|
4
|
+
|
|
5
|
+
function Screen({children, style}) {
|
|
6
|
+
return (
|
|
7
|
+
<SafeAreaView style={[style, styles.screen]}>
|
|
8
|
+
<View style={[style, styles.view]}>
|
|
9
|
+
{children}
|
|
10
|
+
</View>
|
|
11
|
+
</SafeAreaView>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const styles = StyleSheet.create({
|
|
16
|
+
screen: {
|
|
17
|
+
paddingTop: Constants.statusBarHeight,
|
|
18
|
+
flex: 1,
|
|
19
|
+
height: '100%'
|
|
20
|
+
},
|
|
21
|
+
view: {
|
|
22
|
+
flex: 1
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
export default Screen;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {Text as NativeText, StyleSheet} from "react-native";
|
|
3
|
+
|
|
4
|
+
import defaultStyles from '../config/styles'
|
|
5
|
+
import colors from "../config/colors";
|
|
6
|
+
|
|
7
|
+
function Text({children, style, ...otherProps}) {
|
|
8
|
+
return (
|
|
9
|
+
<NativeText style={[defaultStyles.text, style]} {...otherProps} >
|
|
10
|
+
{children}
|
|
11
|
+
</NativeText>
|
|
12
|
+
)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default Text;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {StyleSheet, TextInput as NativeTextInput, View} from "react-native";
|
|
3
|
+
import {MaterialCommunityIcons} from "@expo/vector-icons";
|
|
4
|
+
|
|
5
|
+
import useStyle from "../hooks/useStyle";
|
|
6
|
+
|
|
7
|
+
function TextInput({ icon, width = '100%', ...otherProps }) {
|
|
8
|
+
const defaultStyles = useStyle();
|
|
9
|
+
|
|
10
|
+
const styles = StyleSheet.create({
|
|
11
|
+
container: {
|
|
12
|
+
backgroundColor: defaultStyles.formInput.backgroundColor,
|
|
13
|
+
borderRadius: 25,
|
|
14
|
+
flexDirection: 'row',
|
|
15
|
+
padding: 15,
|
|
16
|
+
marginVertical: 10,
|
|
17
|
+
},
|
|
18
|
+
input: {
|
|
19
|
+
flex: 1,
|
|
20
|
+
color: defaultStyles.formInput.color,
|
|
21
|
+
},
|
|
22
|
+
icon: {
|
|
23
|
+
marginRight: 10
|
|
24
|
+
},
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<View style={ [styles.container, {width}] }>
|
|
29
|
+
{icon && <MaterialCommunityIcons name={icon} size={25} color={defaultStyles.colors.medium} style={styles.icon} />}
|
|
30
|
+
<NativeTextInput
|
|
31
|
+
placeholderTextColor={defaultStyles.colors.medium}
|
|
32
|
+
style={[styles.input, defaultStyles.text]}
|
|
33
|
+
{...otherProps}
|
|
34
|
+
/>
|
|
35
|
+
</View>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default TextInput;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {StyleSheet} from "react-native";
|
|
3
|
+
|
|
4
|
+
import Text from '../Text'
|
|
5
|
+
|
|
6
|
+
function ErrorMessage({error, visible}) {
|
|
7
|
+
if (!visible || !error) return null;
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<Text style={styles.error}>{error}</Text>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const styles = StyleSheet.create({
|
|
15
|
+
error: {
|
|
16
|
+
color: 'tomato'
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export default ErrorMessage;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {Formik} from "formik";
|
|
3
|
+
import {View} from "react-native";
|
|
4
|
+
|
|
5
|
+
function Form({initialValues, onSubmit, validationSchema, children}) {
|
|
6
|
+
return (
|
|
7
|
+
<Formik
|
|
8
|
+
initialValues={initialValues}
|
|
9
|
+
onSubmit={onSubmit}
|
|
10
|
+
validationSchema={validationSchema}
|
|
11
|
+
>
|
|
12
|
+
{() => (
|
|
13
|
+
<View>
|
|
14
|
+
{children}
|
|
15
|
+
</View>
|
|
16
|
+
)}
|
|
17
|
+
</Formik>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default Form;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import RNDateTimePicker from '@react-native-community/datetimepicker';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import {Field, useFormikContext} from "formik";
|
|
4
|
+
import DateTimePicker from "../DateTimePicker";
|
|
5
|
+
import {StyleSheet} from "react-native";
|
|
6
|
+
import useStyle from "../../hooks/useStyle";
|
|
7
|
+
|
|
8
|
+
function FormDateTimePicker ({...props}) {
|
|
9
|
+
|
|
10
|
+
const { values, setFieldValue } = useFormikContext();
|
|
11
|
+
const style = useStyle();
|
|
12
|
+
|
|
13
|
+
const styles = StyleSheet.create({
|
|
14
|
+
picker: {
|
|
15
|
+
flex: 1,
|
|
16
|
+
width: '50%',
|
|
17
|
+
backgroundColor: style.backgroundColor,
|
|
18
|
+
borderRadius: 25,
|
|
19
|
+
flexDirection: 'row',
|
|
20
|
+
padding: 15,
|
|
21
|
+
marginVertical: 10,
|
|
22
|
+
marginHorizontal: 10,
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
return <Field style={styles.picker} component={DateTimePicker} value={values[props.name]} {...props} onChange={(event, value) => {
|
|
27
|
+
setFieldValue(props.name, new Date(value));
|
|
28
|
+
}} />
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default FormDateTimePicker;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {useFormikContext} from "formik";
|
|
3
|
+
import {StyleSheet, View} from "react-native";
|
|
4
|
+
|
|
5
|
+
import TextInput from "../TextInput";
|
|
6
|
+
import ErrorMessage from "./ErrorMessage";
|
|
7
|
+
import useStyle from "../../hooks/useStyle";
|
|
8
|
+
|
|
9
|
+
function FormField({name, width, ...otherProps}) {
|
|
10
|
+
const {setFieldTouched, handleChange, errors, values, touched} = useFormikContext();
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<View>
|
|
14
|
+
<TextInput
|
|
15
|
+
onChangeText={handleChange(name)}
|
|
16
|
+
onBlur={() => setFieldTouched(name)}
|
|
17
|
+
value={values[name]}
|
|
18
|
+
width={width}
|
|
19
|
+
{...otherProps}
|
|
20
|
+
/>
|
|
21
|
+
<ErrorMessage error={errors[name]} visible={touched[name]}/>
|
|
22
|
+
</View>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default FormField;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {View} from "react-native";
|
|
3
|
+
import {useFormikContext} from "formik";
|
|
4
|
+
|
|
5
|
+
import ErrorMessage from './ErrorMessage'
|
|
6
|
+
import ImageInputList from '../ImageInputList'
|
|
7
|
+
|
|
8
|
+
function FormImagePicker({ name }) {
|
|
9
|
+
const {setFieldValue, touched, values, errors} = useFormikContext();
|
|
10
|
+
const imageUris = values[name]
|
|
11
|
+
const handleAdd = (uri) => {
|
|
12
|
+
setFieldValue(name, [...imageUris, uri])
|
|
13
|
+
};
|
|
14
|
+
const handleRemove = (uri) => {
|
|
15
|
+
setFieldValue(name, imageUris.filter(imageUri => imageUri !== uri))
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<View>
|
|
20
|
+
<ImageInputList
|
|
21
|
+
imageUris={imageUris}
|
|
22
|
+
onAddImage={handleAdd}
|
|
23
|
+
onRemoveImage={handleRemove}
|
|
24
|
+
/>
|
|
25
|
+
<ErrorMessage error={errors[name]} visible={touched[name]} />
|
|
26
|
+
</View>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default FormImagePicker;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {View} from "react-native";
|
|
3
|
+
|
|
4
|
+
import Picker from '../Picker'
|
|
5
|
+
import PickerItemComponent from '../PickerItemComponent'
|
|
6
|
+
import ErrorMessage from "./ErrorMessage";
|
|
7
|
+
import {useFormikContext} from "formik";
|
|
8
|
+
|
|
9
|
+
function FormPicker({items, name, PickerItemComponent, placeholder, width, numColumns, ...otherProps}) {
|
|
10
|
+
const {setFieldValue, touched, values, errors} = useFormikContext();
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<View>
|
|
14
|
+
<Picker
|
|
15
|
+
items={items}
|
|
16
|
+
numColumns={numColumns}
|
|
17
|
+
onSelectItem={(value) => {
|
|
18
|
+
setFieldValue(name, value);
|
|
19
|
+
}}
|
|
20
|
+
PickerItemComponent={PickerItemComponent}
|
|
21
|
+
placeholder={placeholder}
|
|
22
|
+
selectedItem={values[name]} {...otherProps}
|
|
23
|
+
width={width}
|
|
24
|
+
/>
|
|
25
|
+
<ErrorMessage error={errors[name]} visible={touched[name]} />
|
|
26
|
+
</View>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default FormPicker;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import Button from "../Button";
|
|
4
|
+
import {useFormikContext} from "formik";
|
|
5
|
+
|
|
6
|
+
function SubmitButton({title, ...otherProps}) {
|
|
7
|
+
const {handleSubmit} = useFormikContext();
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<Button color="primary" title={title} onPress={handleSubmit} {...otherProps} />
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default SubmitButton;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { default as Form} from './Form';
|
|
2
|
+
export { default as FormDateTimePicker} from './FormDateTimePicker';
|
|
3
|
+
export { default as FormField} from './FormField';
|
|
4
|
+
export { default as FormImagePicker} from './FormImagePicker';
|
|
5
|
+
export { default as FormPicker} from './FormPicker';
|
|
6
|
+
export { default as ErrorMessage} from './ErrorMessage';
|
|
7
|
+
export { default as SubmitButton} from './SubmitButton';
|
package/hooks/useApi.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {useState} from "react";
|
|
2
|
+
|
|
3
|
+
export default useApi = (apiFunc) => {
|
|
4
|
+
const [error, setError] = useState(false);
|
|
5
|
+
const [headers, setHeaders] = useState([]);
|
|
6
|
+
const [data, setData] = useState([]);
|
|
7
|
+
const [loading, setLoading] = useState(false);
|
|
8
|
+
|
|
9
|
+
const request = async (...args) => {
|
|
10
|
+
setLoading(true);
|
|
11
|
+
const response = await apiFunc(...args);
|
|
12
|
+
setLoading(false);
|
|
13
|
+
setError(!response.ok);
|
|
14
|
+
setHeaders(response.headers);
|
|
15
|
+
setData(response.data);
|
|
16
|
+
return response;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return {data, error, headers, loading, request}
|
|
20
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boneframework/native-components",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Expo Components for Bone Framework",
|
|
5
5
|
"main": "src/Bone.ts",
|
|
6
6
|
"scripts": {
|
|
@@ -31,8 +31,10 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@react-native-async-storage/async-storage": "^1.24.0",
|
|
33
33
|
"apisauce": "^3.0.1",
|
|
34
|
+
"expo-auth-session": "^5.5.2",
|
|
34
35
|
"expo-secure-store": "^13.0.2",
|
|
35
36
|
"jwt-decode": "^4.0.0",
|
|
36
|
-
"react": "18.2.0"
|
|
37
|
+
"react": "18.2.0",
|
|
38
|
+
"yup": "^1.4.0"
|
|
37
39
|
}
|
|
38
40
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { router } from 'expo-router';
|
|
2
|
+
import React, {useState} from 'react';
|
|
3
|
+
import {Image, ImageBackground, Keyboard, StyleSheet, TouchableOpacity, View} from "react-native";
|
|
4
|
+
import * as Yup from 'yup'
|
|
5
|
+
|
|
6
|
+
import ActivityIndicator from "../components/ActivityIndicator";
|
|
7
|
+
import useApi from "../hooks/useApi";
|
|
8
|
+
import usersApi from '../api/users';
|
|
9
|
+
import {ErrorMessage, FormField, Form, SubmitButton} from '../components/forms'
|
|
10
|
+
import Icon from "../components/Icon";
|
|
11
|
+
|
|
12
|
+
const validationSchema = Yup.object().shape({
|
|
13
|
+
email: Yup.string().required().email().label('Email'),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
function RegisterScreen({postRegisterUrl}) {
|
|
17
|
+
const registerApi = useApi(usersApi.register);
|
|
18
|
+
const [error, setError] = useState();
|
|
19
|
+
const onClose = () => router.back();
|
|
20
|
+
|
|
21
|
+
const handleSubmit = async userInfo => {
|
|
22
|
+
Keyboard.dismiss();
|
|
23
|
+
const result = await registerApi.request(userInfo);
|
|
24
|
+
|
|
25
|
+
if (!result.ok) {
|
|
26
|
+
if (result.data) {
|
|
27
|
+
setError(result.data.error);
|
|
28
|
+
} else {
|
|
29
|
+
setError('An unexpected error occured');
|
|
30
|
+
console.error(result);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
router.navigate(postRegisterUrl);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<>
|
|
41
|
+
<ActivityIndicator visible={registerApi.loading} type={'overlay'}/>
|
|
42
|
+
<ImageBackground blurRadius={10} style={styles.background} source={require('../assets/background.png')} >
|
|
43
|
+
<View style={styles.container}>
|
|
44
|
+
<TouchableOpacity style={styles.cancelButton} onPress={onClose}>
|
|
45
|
+
<Icon size={75} name={'close'} />
|
|
46
|
+
</TouchableOpacity>
|
|
47
|
+
<Image style={styles.logo} source={require('../assets/logo.png')} />
|
|
48
|
+
|
|
49
|
+
<Form
|
|
50
|
+
initialValues={{ email: ''}}
|
|
51
|
+
onSubmit={handleSubmit}
|
|
52
|
+
validationSchema={validationSchema}
|
|
53
|
+
>
|
|
54
|
+
<ErrorMessage error={error} visible={error} />
|
|
55
|
+
<FormField
|
|
56
|
+
name="email"
|
|
57
|
+
icon="email"
|
|
58
|
+
placeholder="Email"
|
|
59
|
+
autoCapitalize="none"
|
|
60
|
+
keyboardType="email-address"
|
|
61
|
+
textContentType="emailAddress"
|
|
62
|
+
/>
|
|
63
|
+
<SubmitButton color="primary" title="Register" />
|
|
64
|
+
</Form>
|
|
65
|
+
</View>
|
|
66
|
+
</ImageBackground>
|
|
67
|
+
</>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const styles = StyleSheet.create({
|
|
72
|
+
container: {
|
|
73
|
+
paddingHorizontal: 20,
|
|
74
|
+
},
|
|
75
|
+
logo: {
|
|
76
|
+
width: 150,
|
|
77
|
+
height: 105,
|
|
78
|
+
alignSelf: "center",
|
|
79
|
+
marginTop: 50,
|
|
80
|
+
marginBottom: 20,
|
|
81
|
+
},
|
|
82
|
+
background: {
|
|
83
|
+
height: '100%'
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
export default RegisterScreen;
|