//App.js
/*import React from 'react';
import {NavigationContainer} from '@react-navigation/native';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
import SignUp from './src/screens/SignUp';
import SignIn from './src/screens/SignIn';
import Dashboard from './src/screens/Dashboard';
const Stack = createNativeStackNavigator();
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator >
<Stack.Screen name="SignIn" component={SignIn} />
<Stack.Screen name="SignUp" component={SignUp} />
<Stack.Screen name="Dashboard" component={Dashboard} />
</Stack.Navigator>
</NavigationContainer>
);
} */
// App.js
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import SplashScreen from './src/screens/SplashScreen';
import SignIn from './src/screens/SignIn';
import SignUp from './src/screens/SignUp';
import Dashboard from './src/screens/Dashboard';
const Stack = createStackNavigator();
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator >
<Stack.Screen name="Splash" component={SplashScreen} options={{headerShown:false}} />
<Stack.Screen name="SignIn" component={SignIn} />
<Stack.Screen name="SignUp" component={SignUp} />
<Stack.Screen name="Dashboard" component={Dashboard} />
</Stack.Navigator>
</NavigationContainer>
);
}
//db.json
{
"users": [
{
"id": "4f16",
"name": "shivam",
"email": "shivam@gmail.com",
"password": "123456",
"mobile": "8299077817"
},
{
"id": "1d17",
"name": "Rohit",
"email": "a@gmail.com",
"password": "123456",
"mobile": "1234564641"
}
],
"tasks": [
{
"title": "shivam pathak ",
"description": "I have completed my task",
"priority": "low",
"status": "to do",
"startDate": "2025-10-30",
"dueDate": "2025-10-30",
"userEmail": "shivam@gmail.com",
"id": "9ffc"
},
{
"id": "8a55",
"title": "Sjsj",
"description": "Sjjssn",
"priority": "low",
"status": "to do",
"startDate": "2025-10-30",
"dueDate": "2025-10-30",
"userEmail": "shivam@gmail.com"
},
{
"id": "b9bf",
"title": "Snsj",
"description": "Znsnsksk\nSjsjsosiksmsksks\nSksosaosiksmsnsnsns",
"priority": "low",
"status": "to do",
"startDate": "2025-10-30",
"dueDate": "2025-10-30",
"userEmail": "shivam@gmail.com"
},
{
"id": "e7c4",
"title": "Znsj",
"description": "Sbsn",
"priority": "low",
"status": "to do",
"startDate": "2025-10-30",
"dueDate": "2025-10-30",
"userEmail": "shivam@gmail.com"
},
{
"id": "632c",
"title": "hello",
"description": "Kya or the ho",
"priority": "urgent",
"status": "in progress",
"startDate": "2025-10-13",
"dueDate": "2025-11-20",
"userEmail": "shivam@gmail.com"
}
]
}
//package.json
{
"name": "todoapp",
"version": "0.0.1",
"private": true,
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"lint": "eslint .",
"start": "react-native start",
"test": "jest"
},
"dependencies": {
"@react-native-async-storage/async-storage": "^2.2.0",
"@react-native-community/datetimepicker": "^8.5.0",
"@react-native-picker/picker": "^2.11.4",
"@react-native/new-app-screen": "0.82.1",
"@react-navigation/native": "^7.1.19",
"@react-navigation/native-stack": "^7.6.1",
"@react-navigation/stack": "^7.6.1",
"axios": "^1.13.1",
"formik": "^2.4.6",
"react": "19.1.1",
"react-native": "0.82.1",
"react-native-gesture-handler": "^2.29.0",
"react-native-picker-select": "^9.3.1",
"react-native-reanimated": "^4.1.3",
"react-native-safe-area-context": "^5.5.2",
"react-native-screens": "^4.18.0",
"react-native-worklets": "^0.6.1",
"yup": "^1.7.1"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@babel/eslint-parser": "^7.28.5",
"@babel/plugin-syntax-jsx": "^7.27.1",
"@babel/preset-env": "^7.25.3",
"@babel/runtime": "^7.25.0",
"@react-native-community/cli": "20.0.0",
"@react-native-community/cli-platform-android": "20.0.0",
"@react-native-community/cli-platform-ios": "20.0.0",
"@react-native/babel-preset": "0.82.1",
"@react-native/eslint-config": "^0.82.1",
"@react-native/metro-config": "0.82.1",
"@react-native/typescript-config": "0.82.1",
"eslint": "^8.57.1",
"jest": "^29.6.3",
"prettier": "2.8.8",
"react-test-renderer": "19.1.1"
},
"engines": {
"node": ">=20"
}
}
// /src/api/api.js
import axios from 'axios';
const API = axios.create({
baseURL: 'http://10.0.2.2:3000', // change if using emulator: Android emulator use 10.0.2.2
timeout: 5000,
});
export default API;
// /src/utils/validation.js
export const isValidEmail = (email) => {
const re = /^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/;
return re.test(String(email).toLowerCase());
};
export const isValidMobile = (mobile) => {
const re = /^\d{10}$/;
return re.test(String(mobile));
};
// /src/screens/Dashboard.js
import React, { useState, useEffect, useMemo, useCallback } from 'react';
import {
View,
Text,
TextInput,
TouchableOpacity,
StyleSheet,
FlatList,
Alert,
Modal,
TouchableWithoutFeedback,
Keyboard,
Platform,
KeyboardAvoidingView,
} from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import API from '../api/api';
import DateTimePicker from '@react-native-community/datetimepicker';
import { Formik } from 'formik';
import * as Yup from 'yup';
import RNPickerSelect from 'react-native-picker-select';
// ----- ProfileCard -----
function ProfileCard({ user, onLogout }) {
return (
<View style={styles.profileCard}>
<View>
<Text style={styles.profileName}>{user.name}</Text>
<Text style={styles.profileMeta}>{user.email}</Text>
<Text style={styles.profileMeta}>{user.mobile}</Text>
</View>
<TouchableOpacity onPress={onLogout} style={styles.logoutBtn}>
<Text style={styles.logoutText}>Logout</Text>
</TouchableOpacity>
</View>
);
}
// ----- TaskCard -----
function TaskCard({ task, onEdit, onDelete }) {
return (
<View style={styles.taskCard}>
<View style={{ flex: 1 }}>
<Text style={styles.taskTitle}>{task.title}</Text>
<Text numberOfLines={2} style={styles.taskDesc}>
{task.description}
</Text>
<View style={styles.taskMetaRow}>
<Text>Priority: {task.priority}</Text>
<Text>Status: {task.status}</Text>
</View>
<Text style={styles.taskDates}>Start: {task.startDate} Due: {task.dueDate}</Text>
</View>
<View style={styles.taskActions}>
<TouchableOpacity onPress={() => onEdit(task)} style={styles.smallBtn}>
<Text>Edit</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => onDelete(task)} style={[styles.smallBtn, styles.deleteBtn]}>
<Text>Delete</Text>
</TouchableOpacity>
</View>
</View>
);
}
// ----- Validation -----
const TaskSchema = Yup.object().shape({
title: Yup.string().trim().required('Title is required'),
description: Yup.string().nullable(),
priority: Yup.string().oneOf(['low', 'medium', 'high', 'urgent']).required('Priority is required'),
status: Yup.string().oneOf(['to do', 'in progress', 'completed']).required('Status is required'),
startDate: Yup.date().required('Start date is required'),
dueDate: Yup.date()
.required('Due date is required')
.min(Yup.ref('startDate'), 'Due date must be same or after start date'),
});
export default function Dashboard({ navigation }) {
const [user, setUser] = useState(null);
const [tasks, setTasks] = useState([]);
const [editingTask, setEditingTask] = useState(null);
const [confirmModal, setConfirmModal] = useState({ visible: false, task: null });
const [showStartPicker, setShowStartPicker] = useState(false);
const [showDuePicker, setShowDuePicker] = useState(false);
const [search, setSearch] = useState('');
const [filterPriority, setFilterPriority] = useState('all');
const [filterStatus, setFilterStatus] = useState('all');
useEffect(() => {
AsyncStorage.getItem('loggedinUser').then(val => {
if (!val) navigation.replace('SignIn');
else setUser(JSON.parse(val));
});
}, [navigation]);
const fetchTasks = useCallback(async () => {
if (!user) return;
try {
const res = await API.get(`/tasks?userEmail=${encodeURIComponent(user.email)}`);
setTasks(res.data || []);
} catch (err) {
console.error(err);
}
}, [user]);
useEffect(() => {
if (user) fetchTasks();
}, [user, fetchTasks]);
const resetEditing = () => setEditingTask(null);
const handleDeleteConfirm = task => setConfirmModal({ visible: true, task });
const handleDelete = async () => {
const t = confirmModal.task;
if (!t) return setConfirmModal({ visible: false, task: null });
try {
await API.delete(`/tasks/${t.id}`);
setConfirmModal({ visible: false, task: null });
fetchTasks();
} catch (err) {
console.error(err);
Alert.alert('Error', 'Could not delete task');
}
};
const handleLogout = async () => {
await AsyncStorage.removeItem('loggedinUser');
navigation.replace('SignIn');
};
const filteredTasks = useMemo(() => {
const s = search.trim().toLowerCase();
return tasks.filter(t => {
if (filterPriority !== 'all' && t.priority !== filterPriority) return false;
if (filterStatus !== 'all' && t.status !== filterStatus) return false;
if (s && !t.title.toLowerCase().includes(s)) return false;
return true;
});
}, [tasks, search, filterPriority, filterStatus]);
const priorityOptions = [
{ label: 'Low', value: 'low' },
{ label: 'Medium', value: 'medium' },
{ label: 'High', value: 'high' },
{ label: 'Urgent', value: 'urgent' },
];
const statusOptions = [
{ label: 'To Do', value: 'to do' },
{ label: 'In Progress', value: 'in progress' },
{ label: 'Completed', value: 'completed' },
];
const filterOptionsAll = [{ label: 'All', value: 'all' }, ...priorityOptions];
const filterStatusAll = [{ label: 'All', value: 'all' }, ...statusOptions];
const formInitialValues = useMemo(
() => ({
title: editingTask?.title ?? '',
description: editingTask?.description ?? '',
priority: editingTask?.priority ?? 'low',
status: editingTask?.status ?? 'to do',
startDate: editingTask ? new Date(editingTask.startDate) : new Date(),
dueDate: editingTask ? new Date(editingTask.dueDate) : new Date(),
}),
[editingTask]
);
const headerElement = useMemo(() => {
return (
<View style={styles.headerWrap}>
<Formik
initialValues={formInitialValues}
validationSchema={TaskSchema}
enableReinitialize
onSubmit={async (values, { setSubmitting, resetForm }) => {
if (!user) {
Alert.alert('Error', 'User not found');
setSubmitting(false);
return;
}
const payload = {
title: values.title,
description: values.description,
priority: values.priority,
status: values.status,
startDate: values.startDate.toISOString().split('T')[0],
dueDate: values.dueDate.toISOString().split('T')[0],
userEmail: user.email,
};
try {
setSubmitting(true);
if (editingTask) {
await API.put(`/tasks/${editingTask.id}`, payload);
Alert.alert('Success', 'Task updated');
} else {
await API.post('/tasks', payload);
Alert.alert('Success', 'Task created');
}
resetForm();
resetEditing();
await fetchTasks();
} catch (err) {
console.error(err);
Alert.alert('Error', 'Could not save task');
} finally {
setSubmitting(false);
}
}}
>
{({ handleChange, handleBlur, handleSubmit, values, setFieldValue, errors, touched, isSubmitting }) => (
<>
<Text style={styles.headerTitle}>{editingTask ? 'Edit Task' : 'Create Task'}</Text>
<TextInput
placeholder="Title"
style={[styles.input, touched.title && errors.title ? styles.inputError : null]}
value={values.title}
onChangeText={handleChange('title')}
onBlur={handleBlur('title')}
/>
{touched.title && errors.title && <Text style={styles.err}>{errors.title}</Text>}
<TextInput
placeholder="Description"
style={[styles.input, styles.descriptionInput, touched.description && errors.description ? styles.inputError : null]}
value={values.description}
onChangeText={handleChange('description')}
onBlur={handleBlur('description')}
multiline
/>
{touched.description && errors.description && <Text style={styles.err}>{errors.description}</Text>}
<View style={styles.row}>
<View style={styles.half}>
<Text style={styles.fieldLabel}>Priority</Text>
<RNPickerSelect
onValueChange={value => setFieldValue('priority', value)}
items={priorityOptions}
value={values.priority}
style={pickerSelectStyles}
useNativeAndroidPickerStyle={false}
/>
{touched.priority && errors.priority && <Text style={styles.err}>{errors.priority}</Text>}
</View>
<View style={styles.half}>
<Text style={styles.fieldLabel}>Status</Text>
<RNPickerSelect
onValueChange={value => setFieldValue('status', value)}
items={statusOptions}
value={values.status}
style={pickerSelectStyles}
useNativeAndroidPickerStyle={false}
/>
{touched.status && errors.status && <Text style={styles.err}>{errors.status}</Text>}
</View>
</View>
<View style={styles.row}>
<TouchableOpacity style={styles.dateBtn} onPress={() => setShowStartPicker(true)}>
<Text>Start: {values.startDate.toISOString().split('T')[0]}</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.dateBtn, { marginLeft: 8 }]} onPress={() => setShowDuePicker(true)}>
<Text>Due: {values.dueDate.toISOString().split('T')[0]}</Text>
</TouchableOpacity>
</View>
{showStartPicker && (
<DateTimePicker
value={values.startDate}
mode="date"
display={Platform.OS === 'ios' ? 'spinner' : 'default'}
onChange={(e, date) => {
setShowStartPicker(false);
if (date) setFieldValue('startDate', date);
}}
/>
)}
{showDuePicker && (
<DateTimePicker
value={values.dueDate}
mode="date"
display={Platform.OS === 'ios' ? 'spinner' : 'default'}
onChange={(e, date) => {
setShowDuePicker(false);
if (date) setFieldValue('dueDate', date);
}}
/>
)}
<TouchableOpacity style={styles.primaryBtn} onPress={handleSubmit} disabled={isSubmitting}>
<Text style={styles.primaryBtnText}>{isSubmitting ? 'Saving...' : editingTask ? 'Update Task' : 'Create Task'}</Text>
</TouchableOpacity>
</>
)}
</Formik>
<View style={styles.divider} />
<TextInput placeholder="Search by title" style={styles.input} value={search} onChangeText={setSearch} />
<View style={styles.row}>
<View style={styles.half}>
<RNPickerSelect
onValueChange={setFilterPriority}
items={filterOptionsAll}
value={filterPriority}
style={pickerSelectStyles}
useNativeAndroidPickerStyle={false}
/>
</View>
<View style={styles.half}>
<RNPickerSelect
onValueChange={setFilterStatus}
items={filterStatusAll}
value={filterStatus}
style={pickerSelectStyles}
useNativeAndroidPickerStyle={false}
/>
</View>
</View>
<Text style={styles.sectionTitle}>Your Tasks</Text>
{filteredTasks.length === 0 && <Text style={styles.noTasks}>No tasks yet.</Text>}
</View>
);
}, [
editingTask,
fetchTasks,
formInitialValues,
user,
search,
filterPriority,
filterStatus,
filteredTasks.length,
showStartPicker,
showDuePicker,
]);
return (
<KeyboardAvoidingView style={{ flex: 1 }} behavior={Platform.OS === 'ios' ? 'padding' : undefined}>
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<View style={styles.container}>
{user && <ProfileCard user={user} onLogout={handleLogout} />}
<FlatList
data={filteredTasks}
keyExtractor={item => String(item.id)}
renderItem={({ item }) => <TaskCard task={item} onEdit={setEditingTask} onDelete={handleDeleteConfirm} />}
ListHeaderComponent={headerElement}
contentContainerStyle={{ paddingBottom: 40 }}
keyboardShouldPersistTaps="handled"
keyboardDismissMode="on-drag"
/>
<Modal transparent visible={confirmModal.visible} animationType="fade">
<View style={styles.modalWrap}>
<View style={styles.modalBox}>
<Text style={styles.modalTitle}>Delete Task</Text>
<Text style={styles.modalText}>Are you sure you want to delete "{confirmModal.task?.title}"?</Text>
<View style={styles.modalActions}>
<TouchableOpacity onPress={() => setConfirmModal({ visible: false, task: null })} style={[styles.smallBtn, { marginRight: 8 }]}>
<Text>Cancel</Text>
</TouchableOpacity>
<TouchableOpacity onPress={handleDelete} style={[styles.smallBtn, styles.modalDeleteBtn]}>
<Text style={styles.modalDeleteText}>Delete</Text>
</TouchableOpacity>
</View>
</View>
</View>
</Modal>
</View>
</TouchableWithoutFeedback>
</KeyboardAvoidingView>
);
}
// ---- Styles ----
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#f7f8fb' },
profileCard: { backgroundColor: '#fff', margin: 16, padding: 16, borderRadius: 16, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', elevation: 3 },
profileName: { fontSize: 18, fontWeight: '700' },
profileMeta: { color: '#334' },
logoutBtn: { backgroundColor: '#ff6b6b', padding: 8, borderRadius: 8 },
logoutText: { color: '#fff' },
headerWrap: { padding: 16 },
headerTitle: { fontSize: 18, fontWeight: '700', marginBottom: 8 },
input: { backgroundColor: '#fff', padding: 12, borderRadius: 12, marginTop: 8, borderWidth: 1, borderColor: '#e6e9ef' },
inputError: { borderColor: '#ff7676' },
descriptionInput: { height: 100, textAlignVertical: 'top' },
row: { flexDirection: 'row', marginTop: 8 },
half: { flex: 1, marginRight: 8 },
dateBtn: { flex: 1, backgroundColor: '#fff', padding: 12, borderRadius: 12, marginTop: 8, borderWidth: 1, borderColor: '#e6e9ef', alignItems: 'center' },
primaryBtn: { backgroundColor: '#0b2545', padding: 14, borderRadius: 12, alignItems: 'center', marginTop: 12 },
primaryBtnText: { color: '#fff', fontWeight: '700' },
divider: { height: 1, backgroundColor: '#eee', marginVertical: 16 },
sectionTitle: { marginTop: 12, fontSize: 18, fontWeight: '700' },
noTasks: { marginTop: 8 },
taskCard: { backgroundColor: '#fff', padding: 12, borderRadius: 12, marginTop: 12, flexDirection: 'row', elevation: 2, marginHorizontal: 16 },
taskTitle: { fontSize: 16, fontWeight: '700' },
taskDesc: { marginTop: 6 },
taskMetaRow: { flexDirection: 'row', marginTop: 8, justifyContent: 'space-between' },
taskDates: { marginTop: 6 },
taskActions: { justifyContent: 'space-between', marginLeft: 12 },
smallBtn: { backgroundColor: '#eaf2ff', padding: 8, borderRadius: 8, alignItems: 'center' },
deleteBtn: { backgroundColor: '#ffdede' },
modalWrap: { flex: 1, backgroundColor: 'rgba(0,0,0,0.4)', justifyContent: 'center', alignItems: 'center' },
modalBox: { backgroundColor: '#fff', padding: 20, borderRadius: 12, width: '85%' },
modalTitle: { fontSize: 18, fontWeight: '700' },
modalText: { marginTop: 8 },
modalActions: { flexDirection: 'row', marginTop: 20, justifyContent: 'flex-end' },
modalDeleteBtn: { backgroundColor: '#ff7676' },
modalDeleteText: { color: '#fff' },
err: { color: '#ff4d4f', marginBottom: 6, marginLeft: 4 },
});
// ---- RNPickerSelect styles for both platforms ----
const pickerSelectStyles = StyleSheet.create({
inputIOS: {
backgroundColor: '#fff',
paddingVertical: 12,
paddingHorizontal: 10,
borderRadius: 12,
borderWidth: 1,
borderColor: '#e6e9ef',
marginTop: 8,
},
inputAndroid: {
backgroundColor: '#fff',
paddingVertical: 8,
paddingHorizontal: 8,
borderRadius: 12,
borderWidth: 1,
borderColor: '#e6e9ef',
marginTop: 8,
},
});
// /src/screens/SignIn.js
import React, { useEffect } from 'react';
import { View, Text, TextInput, TouchableOpacity, StyleSheet, Alert } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import API from '../api/api';
import { Formik } from 'formik';
import * as Yup from 'yup';
const SignInSchema = Yup.object().shape({
email: Yup.string().email('Enter a valid email').required('Email is required'),
password: Yup.string().min(6, 'Password must be at least 6 characters').required('Password is required'),
});
export default function SignIn({ navigation }) {
useEffect(() => {
AsyncStorage.getItem('credentials').then(val => {
if (val) {
// prefilling handled by Formik initialValues if you want
}
});
}, []);
const handleSignIn = async (values, { setSubmitting }) => {
const { email, password } = values;
try {
const res = await API.get(`/users?email=${encodeURIComponent(email)}&password=${encodeURIComponent(password)}`);
if (res.data && res.data.length === 1) {
const user = res.data[0];
await AsyncStorage.setItem('loggedinUser', JSON.stringify(user));
await AsyncStorage.setItem('credentials', JSON.stringify({ email, password }));
navigation.replace('Dashboard');
} else {
Alert.alert('Login failed', 'Invalid credentials');
}
} catch (err) {
console.error(err);
Alert.alert('Error', 'Something went wrong');
} finally {
setSubmitting(false);
}
};
return (
<View style={styles.container}>
<Text style={styles.title}>Welcome Back</Text>
<Formik
initialValues={{ email: '', password: '' }}
validationSchema={SignInSchema}
onSubmit={handleSignIn}
enableReinitialize
>
{({ handleChange, handleBlur, handleSubmit, values, errors, touched, isSubmitting }) => (
<>
<TextInput
placeholder="Email"
style={[styles.input, touched.email && errors.email ? styles.inputError : null]}
value={values.email}
onChangeText={handleChange('email')}
onBlur={handleBlur('email')}
keyboardType="email-address"
autoCapitalize="none"
/>
{touched.email && errors.email && <Text style={styles.err}>{errors.email}</Text>}
<TextInput
placeholder="Password"
style={[styles.input, touched.password && errors.password ? styles.inputError : null]}
value={values.password}
onChangeText={handleChange('password')}
onBlur={handleBlur('password')}
secureTextEntry
/>
{touched.password && errors.password && <Text style={styles.err}>{errors.password}</Text>}
<TouchableOpacity style={styles.button} onPress={handleSubmit} disabled={isSubmitting}>
<Text style={styles.buttonText}>{isSubmitting ? 'Signing in...' : 'Sign In'}</Text>
</TouchableOpacity>
</>
)}
</Formik>
<View style={styles.row}>
<Text>Don't have an account? </Text>
<TouchableOpacity onPress={() => navigation.navigate('SignUp')}>
<Text style={styles.link}>Sign up</Text>
</TouchableOpacity>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 20, justifyContent: 'center', backgroundColor: '#f7f8fb' },
title: { fontSize: 28, fontWeight: '700', marginBottom: 20, color: '#0b2545' }, // moved inline
input: { backgroundColor: '#fff', padding: 12, borderRadius: 12, marginBottom: 8, borderWidth: 1, borderColor: '#e6e9ef' },
inputError: { borderColor: '#ff7676' },
button: { backgroundColor: '#0b2545', padding: 14, borderRadius: 12, alignItems: 'center', marginTop: 8 },
buttonText: { color: '#fff', fontWeight: '600' },
err: { color: '#ff4d4f', marginBottom: 6, marginLeft: 4 },
row: { flexDirection: 'row', marginTop: 16 },
link: { color: '#2b6ef6' },
});
// /src/screens/SignUp.js
import React from 'react';
import { View, Text, TextInput, TouchableOpacity, StyleSheet, Alert, ScrollView } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import API from '../api/api';
import { Formik } from 'formik';
import * as Yup from 'yup';
const SignUpSchema = Yup.object().shape({
name: Yup.string().trim().required('Name is required'),
email: Yup.string().email('Enter a valid email').required('Email is required'),
password: Yup.string().min(6, 'Password must be at least 6 characters').required('Password is required'),
mobile: Yup.string()
.matches(/^\d{10}$/, 'Enter a valid 10-digit mobile number')
.required('Mobile number is required'),
});
export default function SignUp({ navigation }) {
const handleSignUp = async (values, { setSubmitting }) => {
const { name, email, password, mobile } = values;
try {
setSubmitting(true);
const res = await API.get(`/users?email=${encodeURIComponent(email)}`);
if (res.data && res.data.length > 0) {
Alert.alert('Signup failed', 'Email already registered');
setSubmitting(false);
return;
}
const user = { name, email, password, mobile };
await API.post('/users', user);
await AsyncStorage.setItem('credentials', JSON.stringify({ email, password }));
setSubmitting(false);
Alert.alert('Success', 'Account created. Please sign in.', [
{ text: 'OK', onPress: () => navigation.navigate('SignIn') },
]);
} catch (err) {
console.error(err);
setSubmitting(false);
Alert.alert('Error', 'Something went wrong');
}
};
return (
<ScrollView contentContainerStyle={styles.container}>
<Text style={styles.title}>Create Account</Text>
<Formik initialValues={{ name: '', email: '', password: '', mobile: '' }} validationSchema={SignUpSchema} onSubmit={handleSignUp}>
{({ handleChange, handleBlur, handleSubmit, values, errors, touched, isSubmitting }) => (
<>
<TextInput
placeholder="Name"
style={[styles.input, touched.name && errors.name ? styles.inputError : null]}
value={values.name}
onChangeText={handleChange('name')}
onBlur={handleBlur('name')}
/>
{touched.name && errors.name && <Text style={styles.err}>{errors.name}</Text>}
<TextInput
placeholder="Email"
style={[styles.input, touched.email && errors.email ? styles.inputError : null]}
value={values.email}
onChangeText={handleChange('email')}
onBlur={handleBlur('email')}
keyboardType="email-address"
autoCapitalize="none"
/>
{touched.email && errors.email && <Text style={styles.err}>{errors.email}</Text>}
<TextInput
placeholder="Password"
style={[styles.input, touched.password && errors.password ? styles.inputError : null]}
value={values.password}
onChangeText={handleChange('password')}
onBlur={handleBlur('password')}
secureTextEntry
/>
{touched.password && errors.password && <Text style={styles.err}>{errors.password}</Text>}
<TextInput
placeholder="Mobile number"
style={[styles.input, touched.mobile && errors.mobile ? styles.inputError : null]}
value={values.mobile}
onChangeText={handleChange('mobile')}
onBlur={handleBlur('mobile')}
keyboardType="number-pad"
/>
{touched.mobile && errors.mobile && <Text style={styles.err}>{errors.mobile}</Text>}
<TouchableOpacity style={styles.button} onPress={handleSubmit} disabled={isSubmitting}>
<Text style={styles.buttonText}>{isSubmitting ? 'Creating...' : 'Sign Up'}</Text>
</TouchableOpacity>
</>
)}
</Formik>
<View style={styles.row}>
<Text>Already have an account? </Text>
<TouchableOpacity onPress={() => navigation.navigate('SignIn')}>
<Text style={styles.link}>Sign in</Text>
</TouchableOpacity>
</View>
</ScrollView>
);
}
const styles = StyleSheet.create({
container: { flexGrow: 1, padding: 20, justifyContent: 'center', backgroundColor: '#f7f8fb' },
title: { fontSize: 28, fontWeight: '700', marginBottom: 20, color: '#0b2545' },
input: { backgroundColor: '#fff', padding: 12, borderRadius: 12, marginBottom: 8, borderWidth: 1, borderColor: '#e6e9ef' },
inputError: { borderColor: '#ff7676' },
button: { backgroundColor: '#0b2545', padding: 14, borderRadius: 12, alignItems: 'center', marginTop: 8 },
buttonText: { color: '#fff', fontWeight: '600' },
err: { color: '#ff4d4f', marginBottom: 6, marginLeft: 4 },
row: { flexDirection: 'row', marginTop: 16 },
link: { color: '#2b6ef6' },
});
// /src/screens/SplashScreen.js
import React, { useEffect } from 'react';
import { View, Text, StyleSheet, StatusBar } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
export default function SplashScreen({ navigation }) {
useEffect(() => {
const timer = setTimeout(async () => {
try {
const user = await AsyncStorage.getItem('loggedinUser');
if (user) {
navigation.replace('Dashboard');
} else {
navigation.replace('SignIn');
}
} catch (error) {
console.error('Error checking login status:', error);
navigation.replace('SignIn');
}
}, 3000); // show for 3 seconds
return () => clearTimeout(timer); // cleanup
}, [navigation]);
return (
<View style={styles.container}>
<StatusBar backgroundColor="#0b2545" barStyle="light-content" />
{/* <Image
source={require('../assets/logo.png')} // make sure you have /src/assets/logo.png
style={styles.logo}
resizeMode="contain"
/> */}
<Text style={styles.title}>Task Manager</Text>
<Text style={styles.subtitle}>Organize • Track • Achieve</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#0b2545',
justifyContent: 'center',
alignItems: 'center',
},
logo: {
width: 150,
height: 150,
marginBottom: 20,
},
title: {
fontSize: 28,
fontWeight: '700',
color: '#fff',
},
subtitle: {
fontSize: 16,
color: '#cfd6e1',
marginTop: 8,
},
});