Bläddra i källkod

feat: components refactor | formik + yup integration | code refactor

Oleksandr Honcharov 1 år sedan
förälder
incheckning
7c22644617

+ 8 - 26
src/components/AvatarPicker/index.tsx

@@ -1,14 +1,18 @@
 import React, { FC, useState } from 'react';
 import { Image, Text, TouchableOpacity } from 'react-native';
 import * as ImagePicker from 'expo-image-picker';
-import * as FileSystem from 'expo-file-system';
 import AddIcon from '../../../assets/icons/add.svg';
 
 import { styles } from './styles';
+import { ImagePickerAsset } from 'expo-image-picker';
 
-//TODO: simple refactor + download photo
+//TODO: simple refactor
 
-export const AvatarPicker: FC = () => {
+type Props = {
+  selectedAvatar: (avatarPath: ImagePickerAsset) => void;
+};
+
+export const AvatarPicker: FC<Props> = ({ selectedAvatar }) => {
   const [image, setImage] = useState('');
 
   const pickImage = async () => {
@@ -20,31 +24,9 @@ export const AvatarPicker: FC = () => {
       quality: 1
     });
 
-    console.log(result);
-
     if (!result.canceled) {
       setImage(result.assets[0].uri);
-
-      // const uploadResult = await FileSystem.uploadAsync(
-      //   'https://nomadmania.eu/webapi/user/join2',
-      //   result.assets[0].uri,
-      //   {
-      //     httpMethod: 'POST',
-      //     uploadType: FileSystem.FileSystemUploadType.MULTIPART,
-      //     fieldName: 'demo_image',
-      //     headers: {
-      //       nmusername: 'test',
-      //       nmemail: 'test@gmail.com',
-      //       nmpassword: 'testpass123',
-      //       nmfirstname: 'Name',
-      //       nmlastname: 'Lastname',
-      //       nmdateofbirth: `${new Date()}`,
-      //       nmhomebase: 'testid10'
-      //     }
-      //   }
-      // );
-      //
-      // console.log(JSON.stringify(uploadResult));
+      selectedAvatar(result.assets[0]);
     }
   };
   return (

+ 3 - 1
src/components/Calendar/InputDatePicker/index.tsx

@@ -8,11 +8,12 @@ import { Button } from '../../Button';
 
 type Props = {
   selectedDate?: (date: Date) => void;
+  formikError?: string | boolean;
 };
 
 //TODO: waiting for redesign in figma + input fixes
 
-export const InputDatePicker: FC<Props> = ({ selectedDate }) => {
+export const InputDatePicker: FC<Props> = ({ selectedDate, formikError }) => {
   const [visible, setVisible] = useState(false);
   const [spinnerSelectedDate, setSpinnerSelectedDate] = useState<Date>(new Date());
 
@@ -30,6 +31,7 @@ export const InputDatePicker: FC<Props> = ({ selectedDate }) => {
         value={new Date(spinnerSelectedDate).toLocaleDateString()}
         isFocused={(b) => setVisible(b)}
         inputMode={'none'}
+        formikError={formikError}
       />
       <Modal
         visibleInPercent={'50%'}

+ 3 - 3
src/components/FlatList/index.tsx

@@ -6,15 +6,15 @@ import { Item, ItemData } from './item';
 
 const DATA: ItemData[] = [
   {
-    id: 'bd7acbea-c1b1-46c2-aed5-3ad53abb28ba',
+    id: '1',
     title: 'First Item'
   },
   {
-    id: '3ac68afc-c605-48d3-a4f8-fbd91aa97f63',
+    id: '2',
     title: 'Second Item'
   },
   {
-    id: '58694a0f-3da1-471f-bd96-145571e29d72',
+    id: '3',
     title: 'Third Item'
   }
 ];

+ 22 - 4
src/components/Input/index.tsx

@@ -1,5 +1,12 @@
 import React, { FC, useState } from 'react';
-import { TextInput, Text, View, InputModeOptions } from 'react-native';
+import {
+  TextInput,
+  Text,
+  View,
+  InputModeOptions,
+  NativeSyntheticEvent,
+  TextInputFocusEventData
+} from 'react-native';
 import { styling } from './style';
 
 type Props = {
@@ -10,6 +17,9 @@ type Props = {
   inputMode?: InputModeOptions;
   isFocused?: (boolean: boolean) => void;
   value?: string;
+  active?: boolean;
+  onBlur?: (e: NativeSyntheticEvent<TextInputFocusEventData>) => void;
+  formikError?: string | boolean;
 };
 
 export const Input: FC<Props> = ({
@@ -19,7 +29,9 @@ export const Input: FC<Props> = ({
   isPrivate,
   inputMode,
   isFocused,
-  value
+  onBlur,
+  value,
+  formikError
 }) => {
   const [focused, setFocused] = useState(false);
 
@@ -40,9 +52,15 @@ export const Input: FC<Props> = ({
             isFocused(true);
           }
         }}
-        onBlur={() => setFocused(false)}
-        style={styles.wrapper}
+        onBlur={(e) => {
+          setFocused(false);
+          if (onBlur) {
+            onBlur(e);
+          }
+        }}
+        style={[styles.wrapper, formikError ? styles.inputError : null]}
       />
+      {formikError ? <Text style={styles.errorText}>{formikError}</Text> : null}
     </View>
   );
 };

+ 10 - 0
src/components/Input/style.ts

@@ -4,6 +4,10 @@ import { getFontSize } from '../../utils';
 
 export const styling = (focused: boolean) =>
   StyleSheet.create({
+    inputError: {
+      borderStyle: 'solid',
+      borderColor: Colors.RED
+    },
     wrapper: {
       width: '100%',
       color: Colors.DARK_BLUE,
@@ -21,5 +25,11 @@ export const styling = (focused: boolean) =>
       fontSize: getFontSize(14),
       fontFamily: 'redhat-700',
       marginBottom: 5
+    },
+    errorText: {
+      color: Colors.RED,
+      fontSize: getFontSize(12),
+      fontFamily: 'redhat-600',
+      marginTop: 5
     }
   });

+ 9 - 1
src/components/Modal/index.tsx

@@ -1,5 +1,5 @@
 import React, { FC, ReactNode } from 'react';
-import { DimensionValue, Modal as ReactModal, View } from 'react-native';
+import { Dimensions, DimensionValue, Modal as ReactModal, PixelRatio, View } from 'react-native';
 import { ModalHeader } from './ModalHeader/modal-header';
 import { styles } from './style';
 
@@ -11,6 +11,8 @@ type Props = {
   headerTitle: string;
 };
 
+// TODO: heightPercentageToDP for all devices
+
 export const Modal: FC<Props> = ({
   children,
   onRequestClose,
@@ -18,6 +20,12 @@ export const Modal: FC<Props> = ({
   visibleInPercent,
   headerTitle
 }) => {
+  // const heightPercentageToDP = (heightPercent: string) => {
+  //   const screenHeight = Dimensions.get('window').height;
+  //   const elemHeight = parseFloat(heightPercent);
+  //   return PixelRatio.roundToNearestPixel((screenHeight * elemHeight) / 100);
+  // };
+
   return (
     <ReactModal
       animationType={'slide'}

+ 117 - 19
src/screens/RegisterScreen/EditAccount/index.tsx

@@ -1,35 +1,133 @@
-import React from 'react';
+import React, { useState } from 'react';
 import { View, ScrollView } from 'react-native';
+import { Formik } from 'formik';
+import * as yup from 'yup';
+
 import { AvatarPicker, BigText, Button, Header, Input, PageWrapper } from '../../../components';
 import { InputDatePicker } from '../../../components/Calendar/InputDatePicker';
 import { ModalFlatList } from '../../../components/FlatList/modal-flatlist';
+import store from '../../../storage/zustand';
+import {
+  usePostRegister,
+  UserRegistrationData
+} from '../../../modules/auth/api/queries/use-post-register';
+
+const SignUpSchema = yup.object({
+  first_name: yup.string().required(),
+  last_name: yup.string().required(),
+  date_of_birth: yup.date().required(),
+  homebase: yup.number().required()
+});
 
-//TODO: connect with API + simple refactor
+//TODO: formik avatar | date and flatlist error shown
 
 const EditAccount = () => {
+  const [requestData, setRequestData] = useState<UserRegistrationData>({
+    user: {
+      username: '',
+      last_name: '',
+      first_name: '',
+      homebase: 1,
+      date_of_birth: new Date(),
+      email: '',
+      password: ''
+    },
+    photo: {
+      type: '',
+      name: '',
+      uri: ''
+    }
+  });
+
+  const [user] = store((state) => [state.registration.user]);
+
+  const { data, error, refetch } = usePostRegister(
+    {
+      user: requestData.user,
+      photo: requestData.photo
+    },
+    false
+  );
+
   return (
     <PageWrapper>
       <ScrollView showsVerticalScrollIndicator={false}>
         <View style={{ gap: 10 }}>
           <Header label={'Sign Up'} />
           <BigText>Edit account data</BigText>
-          <View style={{ display: 'flex', alignItems: 'center' }}>
-            <AvatarPicker />
-          </View>
-          <Input placeholder={'Text'} onChange={() => {}} header={'First name'} />
-          <Input placeholder={'Text'} onChange={() => {}} header={'Last name'} />
-          <InputDatePicker selectedDate={(date) => console.log(date)} />
-          <ModalFlatList
-            headerTitle={'Region of origin'}
-            selectedObject={(data) => console.log(data)}
-          />
-          <ModalFlatList
-            headerTitle={'Second region'}
-            selectedObject={(data) => console.log(data)}
-          />
-          <View style={{ marginTop: 10 }}>
-            <Button>Sign up</Button>
-          </View>
+          <Formik
+            initialValues={{
+              photo: {
+                type: '',
+                uri: '',
+                name: ''
+              },
+              first_name: '',
+              last_name: '',
+              date_of_birth: new Date(),
+              homebase: 0
+            }}
+            validationSchema={SignUpSchema}
+            onSubmit={(values) => {
+              const data = {
+                user: {
+                  ...user,
+                  first_name: values.first_name,
+                  last_name: values.last_name,
+                  date_of_birth: values.date_of_birth,
+                  homebase: values.homebase
+                },
+                photo: {
+                  type: values.photo.type,
+                  uri: values.photo.uri,
+                  name: values.photo.uri.split('/').pop()!
+                }
+              };
+
+              setRequestData(data);
+
+              refetch();
+            }}
+          >
+            {(props) => (
+              <View>
+                <View style={{ display: 'flex', alignItems: 'center' }}>
+                  <AvatarPicker selectedAvatar={(asset) => props.setFieldValue('photo', asset)} />
+                </View>
+                <Input
+                  onChange={props.handleChange('first_name')}
+                  value={props.values.first_name}
+                  onBlur={props.handleBlur('first_name')}
+                  placeholder={'Enter your first name'}
+                  header={'First name'}
+                  formikError={props.touched.first_name && props.errors.first_name}
+                />
+                <Input
+                  onChange={props.handleChange('last_name')}
+                  value={props.values.last_name}
+                  onBlur={props.handleBlur('last_name')}
+                  placeholder={'Enter your last name'}
+                  header={'Last name'}
+                  formikError={props.touched.last_name && props.errors.last_name}
+                />
+                <InputDatePicker
+                  selectedDate={(date) => props.setFieldValue('date_of_birth', date)}
+                  formikError={props.touched.date_of_birth && props.errors.date_of_birth}
+                />
+                <ModalFlatList
+                  headerTitle={'Region of origin'}
+                  selectedObject={(data) => props.setFieldValue('homebase', data.id)}
+                />
+                <ModalFlatList
+                  headerTitle={'Second region'}
+                  selectedObject={(data) => console.log(data)}
+                />
+                <View style={{ marginTop: 10 }}>
+                  <Button onPress={props.handleSubmit}>Sign up</Button>
+                </View>
+              </View>
+            )}
+          </Formik>
         </View>
       </ScrollView>
     </PageWrapper>

+ 92 - 31
src/screens/RegisterScreen/JoinUs/index.tsx

@@ -1,47 +1,108 @@
-import React, { FC, useState } from 'react';
-import { View } from 'react-native';
+import React, { FC } from 'react';
+import { View, Text, ScrollView } from 'react-native';
 import { NavigationProp } from '@react-navigation/native';
+import { Formik } from 'formik';
+import * as yup from 'yup';
 
 import { PageWrapper, Header, Button, BigText, CheckBox, Input } from '../../../components';
 import { NAVIGATION_PAGES } from '../../../types';
+import store from '../../../storage/zustand';
 
 type Props = {
   navigation: NavigationProp<any>;
 };
 
+const JoinSchema = yup.object({
+  username: yup.string().required('username is required').min(4),
+  email: yup.string().required('email is required').email('enter valid email'),
+  password: yup.string().required('password is required').min(8),
+  repeatPassword: yup
+    .string()
+    .required('repeat your password')
+    .oneOf([yup.ref('password'), 'repeat password'], 'passwords must match'),
+  checkboxAgreed: yup.bool().isTrue()
+});
+
 const JoinUsScreen: FC<Props> = ({ navigation }) => {
-  const [agreed, setAgreed] = useState(false);
+  const [updateRegistrationUserData] = store((state) => [
+    state.registration.updateRegistrationUserData
+  ]);
 
   return (
     <PageWrapper>
-      <Header label={'Sign Up'} />
-      <View style={{ gap: 15 }}>
-        <BigText>Join us. It's free!</BigText>
-        <Input onChange={() => {}} placeholder={'Text'} header={'Username'} />
-        <Input
-          onChange={() => {}}
-          placeholder={'Email'}
-          inputMode={'email'}
-          header={'Email address'}
-        />
-        <Input onChange={() => {}} placeholder={'Text'} isPrivate={true} header={'Password'} />
-        <Input
-          onChange={() => {}}
-          placeholder={'Text'}
-          isPrivate={true}
-          header={'Confirm password'}
-        />
-        <CheckBox
-          label={'I accept NM Terms & Conditions'}
-          onChange={(b) => setAgreed(b)}
-          value={agreed}
-        />
-      </View>
-      <View style={{ marginTop: '15%' }}>
-        <Button onPress={() => navigation.navigate(NAVIGATION_PAGES.REGISTER_ACCOUNT_DATA)}>
-          Continue
-        </Button>
-      </View>
+      <ScrollView showsVerticalScrollIndicator={false}>
+        <Header label={'Sign Up'} />
+        <View style={{ gap: 15 }}>
+          <BigText>Join us. It's free!</BigText>
+          <Formik
+            initialValues={{
+              email: '',
+              username: '',
+              password: '',
+              repeatPassword: '',
+              checkboxAgreed: false
+            }}
+            validationSchema={JoinSchema}
+            onSubmit={(values) => {
+              navigation.navigate(NAVIGATION_PAGES.REGISTER_ACCOUNT_DATA);
+
+              const { email, username, password } = values;
+              updateRegistrationUserData({ email, username, password });
+            }}
+          >
+            {(props) => (
+              <View style={{ gap: 15 }}>
+                <Input
+                  onChange={props.handleChange('username')}
+                  value={props.values.username}
+                  onBlur={props.handleBlur('username')}
+                  placeholder={'Enter your username'}
+                  header={'Username'}
+                  formikError={props.touched.username && props.errors.username}
+                />
+                <Input
+                  onChange={props.handleChange('email')}
+                  value={props.values.email}
+                  onBlur={props.handleBlur('email')}
+                  placeholder={'Enter your email'}
+                  header={'Email address'}
+                  inputMode={'email'}
+                  formikError={props.touched.email && props.errors.email}
+                />
+                <Input
+                  onChange={props.handleChange('password')}
+                  value={props.values.password}
+                  onBlur={props.handleBlur('password')}
+                  placeholder={'Enter your password'}
+                  header={'Password'}
+                  isPrivate={true}
+                  formikError={props.touched.password && props.errors.password}
+                />
+                <Input
+                  onChange={props.handleChange('repeatPassword')}
+                  value={props.values.repeatPassword}
+                  onBlur={props.handleBlur('repeatPassword')}
+                  placeholder={'Repeat your password'}
+                  header={'Confirm password'}
+                  isPrivate={true}
+                  formikError={props.touched.repeatPassword && props.errors.repeatPassword}
+                />
+                <CheckBox
+                  label={'I accept NM Terms & Conditions'}
+                  onChange={(value) => props.setFieldValue('checkboxAgreed', value)}
+                  value={props.values.checkboxAgreed}
+                />
+                <Text>
+                  {props.touched.checkboxAgreed && props.errors.checkboxAgreed
+                    ? 'To use our service you need to agree'
+                    : null}
+                </Text>
+                <Button onPress={props.handleSubmit}>Continue</Button>
+              </View>
+            )}
+          </Formik>
+        </View>
+      </ScrollView>
     </PageWrapper>
   );
 };