Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions mobile/app/signup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import React from 'react';
import { StyleSheet, View, Button, Text, TextInput, ActivityIndicator, Alert } from 'react-native';
import { Formik } from 'formik';
import * as Yup from 'yup';
import { useRouter } from 'expo-router';

import authApi from '../services/authApi';
import { useApi } from '@/services/useAPI';


const SignUpSchema = Yup.object().shape({
name: Yup.string()
.min(2, 'Too Short!')
.required('Name is required'),
email: Yup.string()
.email('Invalid email')
.required('Email is required'),
password: Yup.string()
.min(8, 'Password must be at least 8 characters')
.required('Password is required'),
confirmPassword: Yup.string()
.oneOf([Yup.ref('password')], 'Passwords must match')
.required('Confirm Password is required'),
});

export default function SignUpScreen() {
const router = useRouter();
const { request: performSignUp, loading, error } = useApi(authApi.signUp);


const handleSubmit = async (values: any) => {
const { name, email, password } = values;
const result = await performSignUp({ name, email, password });

if (result.success) {
Alert.alert(
'Success!',
result.data?.message || 'You have been registered successfully.',
[{ text: 'OK', onPress: () => router.replace('/login') }]
);
}

};

return (
<View style={styles.container}>
<Text style={styles.title}>Create Account</Text>


<Formik
initialValues={{ name: '', email: '', password: '', confirmPassword: '' }}
validationSchema={SignUpSchema}
onSubmit={handleSubmit}
>
{({ handleChange, handleBlur, handleSubmit, values, errors, touched }) => (
<View>
<TextInput
style={styles.input}
placeholder="Name"
onChangeText={handleChange('name')}
onBlur={handleBlur('name')}
value={values.name}
/>
{errors.name && touched.name ? <Text style={styles.errorText}>{errors.name}</Text> : null}

<TextInput
style={styles.input}
placeholder="Email"
onChangeText={handleChange('email')}
onBlur={handleBlur('email')}
value={values.email}
keyboardType="email-address"
autoCapitalize="none"
/>
{errors.email && touched.email ? <Text style={styles.errorText}>{errors.email}</Text> : null}

<TextInput
style={styles.input}
placeholder="Password"
onChangeText={handleChange('password')}
onBlur={handleBlur('password')}
value={values.password}
secureTextEntry
/>
{errors.password && touched.password ? <Text style={styles.errorText}>{errors.password}</Text> : null}

<TextInput
style={styles.input}
placeholder="Confirm Password"
onChangeText={handleChange('confirmPassword')}
onBlur={handleBlur('confirmPassword')}
value={values.confirmPassword}
secureTextEntry
/>
{errors.confirmPassword && touched.confirmPassword ? <Text style={styles.errorText}>{errors.confirmPassword}</Text> : null}


{error && <Text style={styles.errorText}>{error}</Text>}

{loading ? (
<ActivityIndicator size="large" style={styles.button} />
) : (
<Button onPress={() => handleSubmit()} title="Sign Up" />
)}
</View>
)}
</Formik>
</View>
);
}


const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
padding: 20,
},
title: {
fontSize: 28,
fontWeight: 'bold',
textAlign: 'center',
marginBottom: 24,
},
input: {
height: 40,
borderColor: 'gray',
borderWidth: 1,
borderRadius: 5,
marginBottom: 10,
paddingHorizontal: 10,
},
errorText: {
color: 'red',
marginBottom: 10,
},
button: {
marginTop: 10,
},
});
23 changes: 23 additions & 0 deletions mobile/services/authApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import apiClient from './apiClient';
import { AxiosResponse } from 'axios';


interface SignUpPayload {
name: string;
email: string;
password: string;
}


interface SignUpResponse {
message: string;

}

const signUp = (payload: SignUpPayload): Promise<AxiosResponse<SignUpResponse>> => {
return apiClient.post('/auth/signup', payload);
};

export default {
signUp,
};
34 changes: 34 additions & 0 deletions mobile/services/useAPI.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useState } from 'react';
import { AxiosResponse } from 'axios';


type ApiFunc<T, P extends any[]> = (...args: P) => Promise<AxiosResponse<T>>;

export const useApi = <T, P extends any[]>(apiFunc: ApiFunc<T, P>) => {
const [data, setData] = useState<T | null>(null);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);

const request = async (...args: P): Promise<{ success: boolean; data?: T; error?: string }> => {
setLoading(true);
setError(null);
try {
const response = await apiFunc(...args);
setData(response.data);
setLoading(false);
return { success: true, data: response.data };
} catch (err: any) {
const errorMessage = err.response?.data?.message || err.message || 'An unexpected error occurred.';
setError(errorMessage);
setLoading(false);
return { success: false, error: errorMessage };
}
};

return {
data,
error,
loading,
request,
};
};
Loading