Skip to content

Postwires

Primary Menu
  • Home
  • CSS
  • Documentation
  • HTML
  • Java
  • Javascript
  • Questions
  • React js
  • React Native
Watch Video
  • Home
  • Uncategorized
  • todoapp build with latest stack
  • Uncategorized

todoapp build with latest stack

john October 31, 2025
//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,
  },
});

About the Author

john

Administrator

Visit Website View All Posts

Continue Reading

Previous: weather app build with latest technology
Next: global map system

Related Stories

  • Uncategorized
  • React Native

How To Fix Gradle Error In React Native

john November 2, 2025
  • Uncategorized

jdagj

john October 31, 2025
  • Uncategorized

global map system

john October 31, 2025

Recent Posts

  • How To Fix Gradle Error In React Native
  • jdagj
  • global map system
  • todoapp build with latest stack
  • weather app build with latest technology

Categories

  • CSS
  • Documentation
  • HTML
  • Java
  • Javascript
  • Questions
  • React js
  • React Native
  • Uncategorized

You may have missed

  • Uncategorized
  • React Native

How To Fix Gradle Error In React Native

john November 2, 2025
  • Uncategorized

jdagj

john October 31, 2025
  • Uncategorized

global map system

john October 31, 2025
  • Uncategorized

todoapp build with latest stack

john October 31, 2025
  • About Author
  • About Us
  • Contact Us
  • Disclaimer
  • Privacy Policy
Copyright © All rights reserved. | MoreNews by AF themes.