Skip to content

Postwires

Primary Menu
  • Home
  • CSS
  • Documentation
  • HTML
  • Java
  • Javascript
  • Questions
  • React js
  • React Native
Watch Video
  • Home
  • React Native
  • storeapp using react native
  • React Native

storeapp using react native

john October 27, 2025
//packgae.json use npm install

{
  "name": "myStore",
  "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/new-app-screen": "0.81.4",
    "@react-navigation/bottom-tabs": "^7.4.7",
    "@react-navigation/drawer": "^7.5.8",
    "@react-navigation/native": "^7.1.17",
    "@react-navigation/stack": "^7.4.8",
    "express": "^5.1.0",
    "formik": "^2.4.6",
    "react": "19.1.0",
    "react-native": "0.81.4",
    "react-native-dropdown-picker": "^5.4.6",
    "react-native-gesture-handler": "^2.28.0",
    "react-native-image-picker": "^8.2.1",
    "react-native-paper": "^5.14.5",
    "react-native-reanimated": "^4.1.2",
    "react-native-safe-area-context": "^5.6.1",
    "react-native-screens": "^4.16.0",
    "react-native-vector-icons": "^10.3.0",
    "react-native-worklets": "^0.6.0",
    "yup": "^1.7.1"
  },
  "devDependencies": {
    "@babel/core": "^7.25.2",
    "@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.81.4",
    "@react-native/eslint-config": "0.81.4",
    "@react-native/metro-config": "0.81.4",
    "@react-native/typescript-config": "0.81.4",
    "@types/jest": "^29.5.13",
    "@types/react": "^19.1.0",
    "@types/react-test-renderer": "^19.1.0",
    "eslint": "^8.19.0",
    "jest": "^29.6.3",
    "prettier": "2.8.8",
    "react-test-renderer": "19.1.0",
    "typescript": "^5.8.3"
  },
  "engines": {
    "node": ">=20"
  }
}

//


//index.js
///**
 * @format
 */

import { AppRegistry } from 'react-native';
import AppNavigator from './AppNavigator';
import { name as appName } from './app.json';

AppRegistry.registerComponent(appName, () => AppNavigator);

//

//

//
// AppNavigator.js
import { LogBox } from "react-native";
import React, { useContext } from "react";
import { NavigationContainer } from "@react-navigation/native";
import { createStackNavigator } from "@react-navigation/stack";
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
import { createDrawerNavigator } from "@react-navigation/drawer";
import Icon from "react-native-vector-icons/Ionicons";

import { AuthProvider, AuthContext } from "./src/AuthContext";
import { CartProvider } from "./src/CartContext";

import App from "./App";
import Detail from "./Details";
import Cart from "./Cart";
import Profile from "./Profile";
import LoginScreen from "./src/screens/LoginScreen";
import SignupScreen from "./src/screens/SignupScreen";

LogBox.ignoreLogs(["SafeAreaView has been deprecated"]);

const Stack = createStackNavigator();
const Tab = createBottomTabNavigator();
const Drawer = createDrawerNavigator();

function HomeStack() {
  return (
    <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "orange" } }}>
      <Stack.Screen
        name="Infinity Store"
        component={App}
        options={{
          headerTitleStyle: {
            fontWeight: "bold",
            fontStyle: "italic",
            fontSize: 30,
          },
        }}
      />
      <Stack.Screen name="Detail" component={Detail} />
    </Stack.Navigator>
  );
}

function MyTabs({ navigation }) {
  const { cartItems } = React.useContext(require("./src/CartContext").CartContext);
  const totalItems = cartItems.length;

  return (
    <Tab.Navigator
      screenOptions={({ route }) => ({
        tabBarIcon: ({ color, size }) => {
          let iconName;
          if (route.name === "Home") iconName = "home";
          else if (route.name === "Cart") iconName = "cart";
          else if (route.name === "More") iconName = "menu";
          else if (route.name === "Profile") iconName = "person";
          return <Icon name={iconName} size={size} color={color} />;
        },
        headerStyle: { backgroundColor: "orange" },
        tabBarStyle: { backgroundColor: "black" },
        tabBarActiveTintColor: "orange",
        tabBarInactiveTintColor: "white",
      })}
    >
      <Tab.Screen name="Home" component={HomeStack} options={{ headerShown: false }} />
      <Tab.Screen
        name="Cart"
        component={Cart}
        options={{
          tabBarBadge: totalItems > 0 ? totalItems : null,
          tabBarBadgeStyle: { backgroundColor: "red", color: "white" },
        }}
      />
      <Tab.Screen name="Profile" component={Profile} />

      {/* ✅ “More” tab now clean + opens drawer */}
    <Tab.Screen name="More" component={() => null} listeners={{
        tabPress: (e) => {
          e.preventDefault();
          navigation.openDrawer();
        }
      }} />
    </Tab.Navigator>
  );
}


function MyDrawer() {
  return (
    <Drawer.Navigator screenOptions={{ drawerType: "front" }}>
      <Drawer.Screen
        name="MainTabs"
        component={MyTabs}
        options={{ headerShown: false, title: "Menu" }}
      />
      <Drawer.Screen
        name="CartDrawer"
        component={MyTabs}
        options={{ headerShown: false, drawerLabel: "Cart" }}
        listeners={({ navigation }) => ({
          drawerItemPress: (e) => {
            e.preventDefault();
            navigation.navigate("MainTabs", { screen: "Cart" });
          },
        })}
      />
      <Drawer.Screen
        name="ProfileDrawer"
        component={MyTabs}
        options={{ headerShown: false, drawerLabel: "Profile" }}
        listeners={({ navigation }) => ({
          drawerItemPress: (e) => {
            e.preventDefault();
            navigation.navigate("MainTabs", { screen: "Profile" });
          },
        })}
      />
    </Drawer.Navigator>
  );
}

// Auth stack (Login / Signup)
function AuthStack() {
  return (
    <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "orange" } }}>
      <Stack.Screen name="Login" component={LoginScreen} options={{ headerShown: false }} />
      <Stack.Screen name="Signup" component={SignupScreen} options={{ headerShown: false }} />
    </Stack.Navigator>
  );
}

// Root Navigation
function RootNavigation() {
  const { user, loading } = useContext(AuthContext);

  if (loading) return null;

  return (
    <Stack.Navigator screenOptions={{ headerShown: false }}>
      {user ? (
        <Stack.Screen name="MyDrawer" component={MyDrawer} />
      ) : (
        <Stack.Screen name="Auth" component={AuthStack} />
      )}
    </Stack.Navigator>
  );
}

export default function AppNavigator() {
  return (
    <AuthProvider>
      <CartProvider>
        <NavigationContainer>
          <RootNavigation />
        </NavigationContainer>
      </CartProvider>
    </AuthProvider>
  );
}
//

//

//App.js


import React, { useState, useEffect } from "react";
import {
  View,
  Text,
  Button,
  FlatList,
  StyleSheet,
  Image,
  TextInput,
  TouchableOpacity,
} from "react-native";
import DropDownPicker from "react-native-dropdown-picker";

const Home = ({ navigation }) => {
  // State for products and search
  const [product, setProduct] = useState([]);
  const [search, setSearch] = useState("");

  // Category dropdown
  const [categoryOpen, setCategoryOpen] = useState(false);
  const [selectedCategory, setSelectedCategory] = useState("");
  const [categoryItems, setCategoryItems] = useState([
    { label: "All Category", value: "" },
    { label: "Electronics", value: "electronics" },
    { label: "Jewelery", value: "jewelery" },
    { label: "Men's clothing", value: "men's clothing" },
    { label: "Women's clothing", value: "women's clothing" },
  ]);

  // Price dropdown
  const [priceOpen, setPriceOpen] = useState(false);
  const [selectedPriceRange, setSelectedPriceRange] = useState("");
  const [priceItems, setPriceItems] = useState([
    { label: "All Prices", value: "" },
    { label: "0 - 100", value: "0-100" },
    { label: "100 - 200", value: "100-200" },
    { label: "200 - 500", value: "200-500" },
    { label: "500 - 1000", value: "500-1000" },
  ]);

  // Fetch products from API
  useEffect(() => {
    fetch("https://fakestoreapi.com/products")
      .then((res) => res.json())
      .then((data) => setProduct(data))
      .catch((err) => console.error(err));
  }, []);

  // Filter function (search + category + price)
  const filter = () => {
    const term = search.trim().toLowerCase();

    return product.filter((p) => {
      const matchTerm = !term || p.title.toLowerCase().includes(term);
      const matchCategory =
        !selectedCategory ||
        p.category.toLowerCase() === selectedCategory.toLowerCase();

      let matchPrice = true;
      if (selectedPriceRange) {
        const [min, max] = selectedPriceRange.split("-").map(Number);
        matchPrice = p.price >= min && p.price <= max;
      }

      return matchTerm && matchCategory && matchPrice;
    });
  };

  // Render UI
  const filteredProducts = filter();

  return (
    <View style={styles.container}>
      <View style={{ backgroundColor: "orange" }}>
        {/* Search Box */}
        <View style={styles.searchBox}>
          <TextInput
            style={styles.searchInput}
            placeholder="Search..."
            placeholderTextColor="black"
            onChangeText={setSearch}
            value={search}
          />
        </View>

        {/* Dropdowns */}
        <View style={styles.dropdownRow}>
          {/* Category Dropdown */}
          <View style={styles.dropdownWrapper}>
            <DropDownPicker
              open={categoryOpen}
              value={selectedCategory}
              items={categoryItems}
              setOpen={setCategoryOpen}
              setValue={setSelectedCategory}
              setItems={setCategoryItems}
              style={styles.dropdown}
              dropDownContainerStyle={styles.dropdownContainer}
              textStyle={{ color: "black" }}
            />
          </View>

          {/* Price Dropdown */}
          <View style={styles.dropdownWrapper}>
            <DropDownPicker
              open={priceOpen}
              value={selectedPriceRange}
              items={priceItems}
              setOpen={setPriceOpen}
              setValue={setSelectedPriceRange}
              setItems={setPriceItems}
              style={styles.dropdown}
              dropDownContainerStyle={styles.dropdownContainer}
              textStyle={{ color: "black" }}
            />
          </View>
        </View>
      </View>

      {/* Product List */}
      {filteredProducts.length === 0 ? (
        <View style={styles.notFoundContainer}>
          <Text style={styles.notFoundText}>No items found </Text>
        </View>
      ) : (
        <FlatList
          data={filteredProducts}
          keyExtractor={(item) => item.id.toString()}
          renderItem={({ item }) => (
            <TouchableOpacity
              onPress={() =>
                navigation.navigate("Detail", { product: item })
              }
              activeOpacity={0.95}
            >
              <View style={styles.card}>
                <Image source={{ uri: item.image }} style={styles.image} />
                <View style={{ flexShrink: 1 }}>
                  <Text style={styles.title}>{item.title}</Text>
                  <Text style={styles.price}>Price: ${item.price}</Text>
                  <Text style={styles.category}>Category: {item.category}</Text>
                  <Text style={styles.rating}>⭐ {item.rating.rate}</Text>

                  <Button
                    color="orange"
                    title="Details"
                    onPress={() =>
                      navigation.navigate("Detail", { product: item })
                    }
                  />
                </View>
              </View>
            </TouchableOpacity>
          )}
        />
      )}
    </View>
  );
};

export default Home;

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#f2f2f2",
    paddingTop: 0,
  },

  // Search Box
  searchBox: {
    borderWidth: 2,
    borderColor: "orange",
    borderRadius: 20,
    marginHorizontal: 10,
    marginBottom: 10,
    marginTop: 10,
  },
  searchInput: {
    color: "black",
    padding: 10,
    backgroundColor: "white",
    borderRadius: 10,
  },

  //  Dropdowns
  dropdownRow: {
    flexDirection: "row",
    marginHorizontal: 10,
    marginBottom: 10,
  },
  dropdownWrapper: {
    flex: 1,
    marginHorizontal: 5,
  },
  dropdown: {
    backgroundColor: "white",
    borderColor: "orange",
    borderWidth: 2,
    borderRadius: 20,
  },
  dropdownContainer: {
    backgroundColor: "white",
    borderColor: "orange",
  },

  //  Product Card
  card: {
    backgroundColor: "white",
    marginBottom: 10,
    padding: 30,
    borderRadius: 5,
    borderWidth: 2,
    borderColor: "orange",
    marginHorizontal: 5,
    flexDirection: "row",
    alignItems: "center",
  },
  image: {
    height: 180,
    width: 180,
    marginRight: 10,
    resizeMode: "contain",
  },
  title: {
    fontSize: 18,
    fontWeight: "500",
    textAlign: "center",
    marginBottom: 10,
  },
  price: {
    fontSize: 16,
    color: "green",
    marginBottom: 10,
  },
  category: {
    fontSize: 14,
    fontStyle: "italic",
    marginBottom: 10,
  },
  rating: {
    fontSize: 14,
    color: "orange",
  },

  //  No items found
  notFoundContainer: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    marginTop: 50,
  },
  notFoundText: {
    fontSize: 18,
    fontWeight: "bold",
    color: "gray",
  },
});



//
//babel.config.js
module.exports = {
  presets: ['module:@react-native/babel-preset'],
   plugins: [
    'react-native-reanimated/plugin', // must be last
  ],
};

//

//

//Cart.js


import { View, Text, FlatList, StyleSheet, Image, Button, TouchableOpacity, Modal } from "react-native";
import { useContext, useState } from "react";
import { CartContext } from "./src/CartContext";

const Cart = () => {
  const { cartItems, increaseQuantity, decreaseQuantity, removeFromCart } =
    useContext(CartContext);

  const [modalVisible, setModalVisible] = useState(false);

  // calculate total price including quantity
  const totalPrice = cartItems.reduce(
    (sum, item) => sum + item.price * item.quantity,
    0
  );

  const handlePayNow = () => {
    if (cartItems.length === 0) {
      return; // do nothing if cart is empty
    }
    setModalVisible(true); // show modal on pay now
  };

  const closeModal = () => {
    setModalVisible(false);
  };

  return (
    <View style={styles.container}>
      {cartItems.length === 0 ? (
        <Text style={styles.empty}>Your cart is empty 🛒</Text>
      ) : (
        <>
          {/* Top view: total price + pay now */}
          <View style={styles.topContainer}>
            <Text style={styles.totalText}>Total: ${totalPrice.toFixed(2)}</Text>
            <TouchableOpacity style={styles.payButton} onPress={handlePayNow}>
              <Text style={styles.payText}>Pay Now</Text>
            </TouchableOpacity>
          </View>

          <FlatList
            data={cartItems}
            keyExtractor={(item) => item.id.toString()}
            renderItem={({ item }) => (
              <View style={styles.card}>
                <Image source={{ uri: item.image }} style={styles.image} />

                <View style={{ flex: 1 }}>
                  <Text style={styles.title}>{item.title}</Text>
                  <Text style={styles.price}>${item.price}</Text>
                  <Text style={styles.quantity}>Qty: {item.quantity}</Text>

                  {/* Buttons Row */}
                  <View style={styles.buttonsRow}>
                    <View style={{ flex: 1, marginHorizontal: 5 }}>
                      <Button
                        color="orange"
                        title="+"
                        onPress={() => increaseQuantity(item.id)}
                      />
                    </View>

                    <View style={{ flex: 1, marginHorizontal: 5 }}>
                      <Button
                        color="orange"
                        title="-"
                        onPress={() => decreaseQuantity(item.id)}
                      />
                    </View>

                    <View style={{ flex: 1, marginHorizontal: 5 }}>
                      <Button
                        title="Remove"
                        color="red"
                        onPress={() => removeFromCart(item.id)}
                      />
                    </View>
                  </View>
                </View>
              </View>
            )}
          />

          {/* Custom Modal for order success */}
          <Modal
            animationType="slide"
            transparent={true}
            visible={modalVisible}
            onRequestClose={closeModal}
          >
            <View style={styles.modalBackground}>
              <View style={styles.modalContainer}>
                <Text style={styles.modalTitle}>Order Placed!</Text>
                <Text style={styles.modalMessage}>
                  Your order has been placed successfully.
                </Text>
                <TouchableOpacity style={styles.modalButton} onPress={closeModal}>
                  <Text style={styles.modalButtonText}>OK</Text>
                </TouchableOpacity>
              </View>
            </View>
          </Modal>
        </>
      )}
    </View>
  );
};

export default Cart;

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 15,
    backgroundColor: "#f2f2f2",
  },
  topContainer: {
    backgroundColor: "white",
    padding: 15,
    borderRadius: 10,
    borderWidth: 2,
    borderColor: "orange",
    marginBottom: 15,
    flexDirection: "row",
    justifyContent: "space-between",
    alignItems: "center",
  },
  totalText: {
    fontSize: 18,
    fontWeight: "bold",
    color: "black",
  },
  payButton: {
    backgroundColor: "green",
    paddingVertical: 8,
    paddingHorizontal: 15,
    borderRadius: 8,
  },
  payText: {
    color: "white",
    fontWeight: "bold",
    fontSize: 16,
  },
  empty: {
    fontSize: 18,
    textAlign: "center",
    marginTop: 50,
    fontWeight: "bold",
    color: "gray",
  },
  card: {
    flexDirection: "row",
    alignItems: "center",
    backgroundColor: "white",
    borderRadius: 15,
    padding: 10,
    marginBottom: 10,
    borderWidth: 2,
    borderColor: "orange",
  },
  image: {
    height: 80,
    width: 80,
    resizeMode: "contain",
    marginRight: 10,
  },
  title: {
    fontSize: 16,
    fontWeight: "600",
  },
  price: {
    fontSize: 14,
    color: "green",
    marginTop: 5,
  },
  quantity: {
    fontSize: 14,
    color: "orange",
    marginTop: 5,
  },
  buttonsRow: {
    flexDirection: "row", // buttons side by side
    justifyContent: "space-between",
    marginTop: 10,
  },

  // Modal styles
  modalBackground: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: "rgba(0,0,0,0.5)",
  },
  modalContainer: {
    width: "80%",
    backgroundColor: "white",
    borderRadius: 15,
    padding: 20,
    alignItems: "center",
  },
  modalTitle: {
    fontSize: 20,
    fontWeight: "bold",
    marginBottom: 10,
  },
  modalMessage: {
    fontSize: 16,
    textAlign: "center",
    marginBottom: 20,
  },
  modalButton: {
    backgroundColor: "orange",
    paddingVertical: 10,
    paddingHorizontal: 25,
    borderRadius: 8,
  },
  modalButtonText: {
    color: "white",
    fontWeight: "bold",
    fontSize: 16,
  },
});

//

//

//db.json
{
  "users": [
    {
      "id": "1",
      "name": "Demo User",
      "email": "demo@example.com",
      "password": "123456",
      "cart": [
        {
          "id": 1,
          "title": "Fjallraven - Foldsack No. 1 Backpack, Fits 15 Laptops",
          "price": 109.95,
          "description": "Your perfect pack for everyday use and walks in the forest. Stash your laptop (up to 15 inches) in the padded sleeve, your everyday",
          "category": "men's clothing",
          "image": "https://fakestoreapi.com/img/81fPKd-2AYL._AC_SL1500_t.png",
          "rating": {
            "rate": 3.9,
            "count": 120
          },
          "quantity": 1
        },
        {
          "id": 2,
          "title": "Mens Casual Premium Slim Fit T-Shirts ",
          "price": 22.3,
          "description": "Slim-fitting style, contrast raglan long sleeve, three-button henley placket, light weight & soft fabric for breathable and comfortable wearing. And Solid stitched shirts with round neck made for durability and a great fit for casual fashion wear and diehard baseball fans. The Henley style round neckline includes a three-button placket.",
          "category": "men's clothing",
          "image": "https://fakestoreapi.com/img/71-3HjGNDUL._AC_SY879._SX._UX._SY._UY_t.png",
          "rating": {
            "rate": 4.1,
            "count": 259
          },
          "quantity": 1
        }
      ],
      "avatar": null
    }]}

//

//

//Details.js

import { View, Text, StyleSheet, Image, Button, Modal, TouchableOpacity,ScrollView } from "react-native";
import { useContext, useState } from "react";
import { CartContext } from "./src/CartContext";

const Detail = ({ route }) => {
  const { product } = route.params;
  const { addToCart } = useContext(CartContext);

  const [modalVisible, setModalVisible] = useState(false);

  const handleAddToCart = () => {
    addToCart(product);
    setModalVisible(true); // ✅ Show modal
  };

  return (
    <View style={styles.container}>
      <ScrollView>
      <View style={styles.card}>
        <Image source={{ uri: product.image }} style={styles.image} />
        <Text style={styles.title}>{product.title}</Text>
        <Text style={styles.price}>Price: ${product.price}</Text>
        <Text style={styles.category}>Category: {product.category}</Text>
        <Text style={styles.description}>{product.description}</Text>
        <Text style={styles.rating}>
          ⭐ {product.rating.rate} ({product.rating.count} reviews)
        </Text>

        <Button color="orange" title="Add to Cart" onPress={handleAddToCart} />
      </View>
      </ScrollView>

      {/* ✅ Success Modal */}
      <Modal
        visible={modalVisible}
        transparent
        animationType="fade"
        onRequestClose={() => setModalVisible(false)}
      >
        <View style={styles.modalBackground}>
          <View style={styles.modalContainer}>
            <Text style={styles.modalText}>✅ Successfully added to cart!</Text>
            <TouchableOpacity
              style={styles.modalButton}
              onPress={() => setModalVisible(false)}
            >
              <Text style={styles.modalButtonText}>OK</Text>
            </TouchableOpacity>
          </View>
        </View>
      </Modal>
    </View>
  );
};

export default Detail;

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 15,
    backgroundColor: "#f2f2f2",
  },
  card: {
    backgroundColor: "white",
    borderRadius: 15,
    padding: 20,
    elevation: 4,
    alignItems: "center",
    borderWidth: 3,
    borderColor: "orange",
    
  },
  image: {
    height: 180,
    width: 180,
    marginBottom: 15,
    resizeMode: "contain",
  },
  title: {
    fontSize: 18,
    fontWeight: "700",
    textAlign: "center",
    marginBottom: 10,
  },
  price: {
    fontSize: 16,
    color: "green",
    marginBottom: 10,
  },
  category: {
    fontSize: 14,
    fontStyle: "italic",
    marginBottom: 10,
  },
  description: {
    fontSize: 14,
    marginBottom: 10,
    textAlign: "center",
  },
  rating: {
    fontSize: 14,
    color: "orange",
    marginBottom: 10,
  },
  modalBackground: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: "rgba(0,0,0,0.5)",
  },
  modalContainer: {
    backgroundColor: "white",
    padding: 25,
    borderRadius: 15,
    alignItems: "center",
    elevation: 5,
    width: "80%",
  },
  modalText: {
    fontSize: 16,
    fontWeight: "bold",
    marginBottom: 15,
    textAlign: "center",
  },
  modalButton: {
    backgroundColor: "orange",
    paddingHorizontal: 25,
    paddingVertical: 10,
    borderRadius: 10,
  },
  modalButtonText: {
    color: "white",
    fontWeight: "bold",
  },
});
//

//

//

//EditProfileModal.js

import React, { useState, useEffect } from "react";
import {
  View,
  Text,
  Modal,
  StyleSheet,
  TouchableOpacity,
  TextInput,
  Image,
  ActivityIndicator,
  Platform,
  PermissionsAndroid,
} from "react-native";
import { launchImageLibrary, launchCamera } from "react-native-image-picker";
import AsyncStorage from "@react-native-async-storage/async-storage";

export default function EditProfileModal({
  visible,
  onClose,
  user,
  apiHost,
  onSaved, 
}) {
  const [name, setName] = useState(user?.name || "");
  const [email, setEmail] = useState(user?.email || "");
  const [password, setPassword] = useState(user?.password || "");
  const [avatarPreview, setAvatarPreview] = useState(user?.avatar || null);
  const [saving, setSaving] = useState(false);
  const [infoMessage, setInfoMessage] = useState(null);
  const [showPassword, setShowPassword] = useState(false);

  useEffect(() => {

    setName(user?.name || "");
    setEmail(user?.email || "");
    setPassword(user?.password || "");
    setAvatarPreview(user?.avatar || null);
    setInfoMessage(null);
    setShowPassword(false);
  }, [user, visible]);

  const USERS_ENDPOINT = `${apiHost}/users`;

  const showInfo = (msg) => {
    setInfoMessage(msg);
    setTimeout(() => setInfoMessage(null), 1500);
  };

  const validate = () => {
    if (!name.trim()) {
      showInfo("Name is required");
      return false;
    }
    if (!email.trim() || !/^\S+@\S+\.\S+$/.test(email)) {
      showInfo("Enter a valid email");
      return false;
    }
    if (password && password.length < 4) {
      showInfo("Password at least 4 chars");
      return false;
    }
    return true;
  };

  const patchUser = async (payload) => {
    const id = user.id;
    const url = `${USERS_ENDPOINT}/${encodeURIComponent(id)}`;
    const resp = await fetch(url, {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(payload),
    });
    if (!resp.ok) {
      const text = await resp.text().catch(() => null);
      throw new Error(`Failed (${resp.status}) ${text || ""}`);
    }
    return resp.json();
  };

  const persistUserLocally = async (updatedUser) => {
    try {
      const stored = await AsyncStorage.getItem("@user_profile");
      if (!stored) {
        await AsyncStorage.setItem("@user_profile", JSON.stringify(updatedUser));
        return;
      }
      const parsed = JSON.parse(stored);
      
      if (parsed && String(parsed.id) === String(updatedUser.id)) {
        await AsyncStorage.setItem("@user_profile", JSON.stringify({ ...parsed, ...updatedUser }));
      } else {
      
        await AsyncStorage.setItem("@user_profile", JSON.stringify(updatedUser));
      }
    } catch (e) {
      console.warn("Persist user local failed", e);
    }
  };

  const handleSave = async () => {
    if (!validate()) return;
    setSaving(true);
    try {
      const payload = {};
      if (name !== user.name) payload.name = name.trim();
      if (email !== user.email) payload.email = email.trim();
      if (password !== user.password) payload.password = password;
      // avatarPreview may be null (remove) or a data URI
      if (avatarPreview !== user.avatar) payload.avatar = avatarPreview === null ? null : avatarPreview;

      if (Object.keys(payload).length === 0) {
        showInfo("No changes");
        onClose();
        return;
      }

      const updated = await patchUser(payload);

      
      await persistUserLocally(updated);

      showInfo("Saved");
      onSaved && onSaved(updated);
     
      setTimeout(onClose, 700);
    } catch (err) {
      console.error("Save profile error:", err);
      showInfo("Save failed");
    } finally {
      setSaving(false);
    }
  };

  const pickImage = async () => {
    try {
      const res = await launchImageLibrary({
        mediaType: "photo",
        includeBase64: true,
        quality: 0.6,
        selectionLimit: 1,
        
      });
      if (res.didCancel) return;
      if (res.errorCode) {
        showInfo("Image error");
        return;
      }
      const asset = res.assets && res.assets[0];
      if (asset && asset.base64) {
        const mime = asset.type || "image/jpeg";
        const dataUri = `data:${mime};base64,${asset.base64}`;
        setAvatarPreview(dataUri); // update preview immediately
      } else showInfo("Unsupported");
    } catch (err) {
      console.error("pickImage error", err);
      showInfo("Pick failed");
    }
  };

  const requestCameraPermissionAndroid = async () => {
    try {
      const granted = await PermissionsAndroid.request(
        PermissionsAndroid.PERMISSIONS.CAMERA,
        {
          title: "Camera Permission",
          message: "App needs camera access to take profile photos.",
          buttonPositive: "OK",
          buttonNegative: "Cancel",
        }
      );
      return granted === PermissionsAndroid.RESULTS.GRANTED;
    } catch (err) {
      console.warn("Camera permission error", err);
      return false;
    }
  };

  const takePhoto = async () => {
    try {
      if (Platform.OS === "android") {
        const ok = await requestCameraPermissionAndroid();
        if (!ok) {
          showInfo("Camera permission denied");
          return;
        }
      }
      const res = await launchCamera({
        mediaType: "photo",
        includeBase64: true,
        quality: 0.6,
        saveToPhotos: false,
      });
      if (res.didCancel) return;
      if (res.errorCode) {
        showInfo("Camera error");
        return;
      }
      const asset = res.assets && res.assets[0];
      if (asset && asset.base64) {
        const mime = asset.type || "image/jpeg";
        const dataUri = `data:${mime};base64,${asset.base64}`;
        setAvatarPreview(dataUri);
      } else showInfo("Unsupported");
    } catch (err) {
      console.error("takePhoto error", err);
      showInfo("Camera failed");
    }
  };

  const removePhoto = () => {
    setAvatarPreview(null);
    showInfo("Photo removed");
  };

  return (
    <Modal visible={visible} animationType="slide" transparent>
      <View style={styles.modalBackdrop}>
        <View style={styles.modalCard}>
          <Text style={styles.title}>Edit Profile</Text>

          <View style={styles.avatarRow}>
            {avatarPreview ? (
              <Image source={{ uri: avatarPreview }} style={styles.avatarLarge} />
            ) : (
              <View style={[styles.avatarLarge, styles.avatarPlaceholder]}>
                <Text style={{ color: "#666" }}>No photo</Text>
              </View>
            )}
          </View>

          <View style={styles.rowButtons}>
            <TouchableOpacity style={styles.smallBtn} onPress={pickImage}>
              <Text style={styles.smallBtnText}>Pick</Text>
            </TouchableOpacity>
            <TouchableOpacity style={[styles.smallBtn, { backgroundColor: "#1c7c54" }]} onPress={takePhoto}>
              <Text style={styles.smallBtnText}>Camera</Text>
            </TouchableOpacity>
            <TouchableOpacity style={[styles.smallBtn, { backgroundColor: "#d9534f" }]} onPress={removePhoto}>
              <Text style={styles.smallBtnText}>Remove</Text>
            </TouchableOpacity>
          </View>

          <TextInput style={styles.input} value={name} onChangeText={setName} placeholder="Name" placeholderTextColor="#888" />

          <TextInput
            style={styles.input}
            value={email}
            onChangeText={setEmail}
            placeholder="Email"
            keyboardType="email-address"
            placeholderTextColor="#888"
          />

          {/* Password field with eye toggle and black text */}
          <View style={styles.passwordRow}>
            <TextInput
              style={[styles.input, { flex: 1, color: "#000" }]}
              value={password}
              onChangeText={setPassword}
              placeholder="Password"
              secureTextEntry={!showPassword}
              placeholderTextColor="#888"
            />
            <TouchableOpacity
              onPress={() => setShowPassword((s) => !s)}
              style={styles.eyeBtn}
              accessibilityLabel="Toggle password visibility"
            >
              <Text style={styles.eyeBtnText}>{showPassword ? "Hide" : "Show"}</Text>
            </TouchableOpacity>
          </View>

          <View style={{ flexDirection: "row", justifyContent: "space-between", marginTop: 10 }}>
            <TouchableOpacity style={[styles.actionBtn, { backgroundColor: "#aaa" }]} onPress={onClose} disabled={saving}>
              <Text style={styles.actionBtnText}>Cancel</Text>
            </TouchableOpacity>

            <TouchableOpacity style={[styles.actionBtn, { backgroundColor: "#0a84ff" }]} onPress={handleSave} disabled={saving}>
              {saving ? <ActivityIndicator color="#fff" /> : <Text style={styles.actionBtnText}>Save</Text>}
            </TouchableOpacity>
          </View>

          {infoMessage && (
            <View style={styles.infoBox}>
              <Text style={{ color: "white" }}>{infoMessage}</Text>
            </View>
          )}
        </View>
      </View>
    </Modal>
  );
}

const styles = StyleSheet.create({
  modalBackdrop: {
    flex: 1,
    backgroundColor: "rgba(0,0,0,0.45)",
    justifyContent: "center",
    padding: 16,
  },
  modalCard: {
    backgroundColor: "white",
    borderRadius: 12,
    padding: 16,
    elevation: 6,
  },
  title: { fontSize: 18, fontWeight: "700", marginBottom: 8, textAlign: "center" },
  avatarLarge: { width: 110, height: 110, borderRadius: 60, marginBottom: 8 },
  avatarPlaceholder: { backgroundColor: "#f0f0f0", alignItems: "center", justifyContent: "center" },
  avatarRow: { alignItems: "center", marginBottom: 8 },
  rowButtons: { flexDirection: "row", justifyContent: "space-between", marginBottom: 8 },
  smallBtn: { flex: 1, padding: 8, marginHorizontal: 4, backgroundColor: "#0a84ff", borderRadius: 6, alignItems: "center" },
  smallBtnText: { color: "white", fontWeight: "600" },
  input: { borderWidth: 1, borderColor: "#ddd", borderRadius: 8, padding: 10, marginTop: 8, color: "#000" },
  passwordRow: { flexDirection: "row", alignItems: "center", marginTop: 8 },
  eyeBtn: { marginLeft: 8, paddingHorizontal: 10, paddingVertical: 8, backgroundColor: "#eee", borderRadius: 8 },
  eyeBtnText: { color: "#333", fontWeight: "700" },
  actionBtn: { flex: 1, marginHorizontal: 6, padding: 12, borderRadius: 8, alignItems: "center" },
  actionBtnText: { color: "white", fontWeight: "700" },
  infoBox: { position: "absolute", bottom: 12, left: 12, right: 12, backgroundColor: "#333", padding: 8, borderRadius: 8, alignItems: "center" },
});


//

//

//Profile.js


import React, { useContext, useState, useEffect } from "react";
import {
  View,
  Text,
  TouchableOpacity,
  Image,
  ActivityIndicator,
  Modal,
  StyleSheet,
} from "react-native";
import { AuthContext } from "./src/AuthContext";
import { CartContext } from "./src/CartContext";
import AsyncStorage from "@react-native-async-storage/async-storage";
import EditProfileModal from "./EditProfileModal";


const API_HOST = "http://192.168.187.112:3000"; 
const USERS_ENDPOINT = `${API_HOST}/users`;

export default function Profile({ navigation }) {
  const { user, signOut } = useContext(AuthContext);
  const { clearCart } = useContext(CartContext);

  const [showLogoutModal, setShowLogoutModal] = useState(false);
  const [showEditModal, setShowEditModal] = useState(false);
  const [localAvatar, setLocalAvatar] = useState(null);
  const [savingIndicator, setSavingIndicator] = useState(false);
  const [infoMessage, setInfoMessage] = useState(null);
  const [localUser, setLocalUser] = useState(user);

  useEffect(() => {
    setLocalUser(user);
    if (user && user.avatar) setLocalAvatar(user.avatar);
  }, [user]);

  if (!user) {
    return (
      <View style={styles.centered}>
        <Text style={styles.text}>You are not logged in.</Text>
      </View>
    );
  }

  const showTempInfo = (msg) => {
    setInfoMessage(msg);
    setTimeout(() => setInfoMessage(null), 1400);
  };

  const handleLogout = async () => {
    setShowLogoutModal(false);
    try {
      await signOut(clearCart);
    } catch (e) {
      console.error("Logout error:", e);
      showTempInfo("Logout failed");
    }
  };

  const handleRemovePhoto = async () => {
    try {
      setSavingIndicator(true);
      const url = `${USERS_ENDPOINT}/${encodeURIComponent(user.id)}`;
      const resp = await fetch(url, {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ avatar: null }),
      });
      if (!resp.ok) throw new Error("Failed");
      const updated = await resp.json();
      setLocalAvatar(null);
      setLocalUser(updated);

     
      try {
        const stored = await AsyncStorage.getItem("@user_profile");
        if (stored) {
          const j = JSON.parse(stored);
          if (j && String(j.id) === String(user.id)) {
            await AsyncStorage.setItem("@user_profile", JSON.stringify({ ...j, avatar: null }));
          }
        } else {
          await AsyncStorage.setItem("@user_profile", JSON.stringify(updated));
        }
      } catch (e) {
        console.warn("AsyncStorage update failed", e);
      }

      showTempInfo("Photo removed");
    } catch (err) {
      console.error("removePhoto error", err);
      showTempInfo("Remove failed");
    } finally {
      setSavingIndicator(false);
    }
  };

  const handleSavedFromModal = async (updatedUser) => {
 
    if (updatedUser) {
      setLocalUser(updatedUser);
      if (updatedUser.avatar !== undefined) setLocalAvatar(updatedUser.avatar);
      try {
        const stored = await AsyncStorage.getItem("@user_profile");
        if (stored) {
          const j = JSON.parse(stored);
          if (j && String(j.id) === String(updatedUser.id)) {
            await AsyncStorage.setItem("@user_profile", JSON.stringify({ ...j, ...updatedUser }));
          } else {
            await AsyncStorage.setItem("@user_profile", JSON.stringify(updatedUser));
          }
        } else {
          await AsyncStorage.setItem("@user_profile", JSON.stringify(updatedUser));
        }
      } catch (e) {
        console.warn("AsyncStorage save fail", e);
      }
    }
    showTempInfo("Profile updated");
  };

  return (
    <View style={styles.container}>
     
      <View style={styles.card}>
        <View style={styles.row}>
          <View style={styles.avatarWrap}>
            {localAvatar ? (
              <Image source={{ uri: localAvatar }} style={styles.avatar} />
            ) : (
              <View style={[styles.avatar, styles.avatarPlaceholder]}>
                <Text style={{ color: "#666" }}>No photo</Text>
              </View>
            )}
          </View>

          <View style={styles.info}>
            <Text style={styles.name}>{localUser?.name || user.name}</Text>
            <Text style={styles.email}>{localUser?.email || user.email}</Text>
            <Text style={styles.email}>Password: {localUser?.password ? "•".repeat(6) : "—"}</Text>
          </View>
        </View>

        <View style={styles.cardButtons}>
          <TouchableOpacity style={styles.primaryBtn} onPress={() => setShowEditModal(true)}>
            <Text style={styles.primaryBtnText}>Edit profile</Text>
          </TouchableOpacity>

          <TouchableOpacity style={[styles.secondaryBtn, { backgroundColor: "#d9534f" }]} onPress={handleRemovePhoto}>
            {savingIndicator ? <ActivityIndicator color="#fff" /> : <Text style={styles.secondaryBtnText}>Remove photo</Text>}
          </TouchableOpacity>
        </View>
      </View>

      
      <View style={styles.footer}>
        <TouchableOpacity style={styles.logoutBtn} onPress={() => setShowLogoutModal(true)}>
          <Text style={styles.logoutBtnText}>Logout</Text>
        </TouchableOpacity>
      </View>

    
      <Modal visible={showLogoutModal} transparent animationType="fade" onRequestClose={() => setShowLogoutModal(false)}>
        <View style={styles.modalOverlay}>
          <View style={styles.modalCard}>
            <Text style={styles.modalText}>Are you sure you want to logout?</Text>
            <View style={styles.modalButtons}>
              <TouchableOpacity style={[styles.modalBtn, { backgroundColor: "gray" }]} onPress={() => setShowLogoutModal(false)}>
                <Text style={styles.modalBtnText}>Cancel</Text>
              </TouchableOpacity>
              <TouchableOpacity style={[styles.modalBtn, { backgroundColor: "red" }]} onPress={handleLogout}>
                <Text style={styles.modalBtnText}>Logout</Text>
              </TouchableOpacity>
            </View>
          </View>
        </View>
      </Modal>

      <EditProfileModal visible={showEditModal} onClose={() => setShowEditModal(false)} user={localUser || user} apiHost={API_HOST} onSaved={handleSavedFromModal} />

      
      {infoMessage && (
        <View style={styles.infoBox}>
          <Text style={{ color: "white" }}>{infoMessage}</Text>
        </View>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, padding: 16, backgroundColor: "#f7f7f7" },
  centered: { flex: 1, alignItems: "center", justifyContent: "center" },
  card: { backgroundColor: "#fff", borderRadius: 12, padding: 16, elevation: 4 },
  row: { flexDirection: "row", alignItems: "center" },
  avatarWrap: { marginRight: 12 },
  avatar: { width: 90, height: 90, borderRadius: 46 },
  avatarPlaceholder: { backgroundColor: "#eee", alignItems: "center", justifyContent: "center" },
  info: { flex: 1 },
  name: { fontSize: 18, fontWeight: "700" },
  email: { fontSize: 14, color: "#666", marginTop: 4 },
  cardButtons: { flexDirection: "row", marginTop: 12, justifyContent: "space-between" },
  primaryBtn: { backgroundColor: "#0a84ff", padding: 12, borderRadius: 8, alignItems: "center", flex: 1, marginRight: 8 },
  primaryBtnText: { color: "white", fontWeight: "700" },
  secondaryBtn: { padding: 12, borderRadius: 8, alignItems: "center", justifyContent: "center", width: 120 },
  secondaryBtnText: { color: "white", fontWeight: "700" },

  footer: { flex: 1, justifyContent: "flex-end", alignItems: "center", marginTop: 18 },
  logoutBtn: { backgroundColor: "#d9534f", padding: 14, borderRadius: 10, width: "100%" },
  logoutBtnText: { color: "white", fontWeight: "700", textAlign: "center" },

  modalOverlay: {
    flex: 1,
    backgroundColor: "rgba(0,0,0,0.4)",
    justifyContent: "center",
    alignItems: "center",
  },
  modalCard: {
    width: "80%",
    backgroundColor: "white",
    borderRadius: 12,
    padding: 20,
    alignItems: "center",
    elevation: 5,
  },
  modalText: { fontSize: 18, fontWeight: "600", marginBottom: 20, textAlign: "center" },
  modalButtons: { flexDirection: "row", justifyContent: "space-between", width: "100%" },
  modalBtn: { flex: 1, padding: 12, borderRadius: 8, marginHorizontal: 5, alignItems: "center" },
  modalBtnText: { color: "white", fontWeight: "600" },

  infoBox: { position: "absolute", bottom: 16, left: 16, right: 16, backgroundColor: "#333", padding: 8, borderRadius: 8, alignItems: "center" },
  text: { fontSize: 16 },
});


//

//

//

//android>app>build.gradle
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"

//

//

//adroid.menifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-permission android:name="android.permission.INTERNET" />

      <!-- Add these permissions -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
    <uses-permission android:name="android.permission.CAMERA" />

    <application
      android:name=".MainApplication"
      android:label="@string/app_name"
      android:icon="@mipmap/ic_launcher"
      android:roundIcon="@mipmap/ic_launcher_round"
      android:allowBackup="false"
      android:theme="@style/AppTheme"
      android:usesCleartextTraffic="${usesCleartextTraffic}"
      android:supportsRtl="true">
      <activity
        android:name=".MainActivity"
        android:label="@string/app_name"
        android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
        android:launchMode="singleTask"
        android:windowSoftInputMode="adjustResize"
        android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
      </activity>
    </application>
</manifest>


//

//

//
// src/AuthContext.js
import React, { createContext, useEffect, useState } from "react";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { Platform } from "react-native";

export const AuthContext = createContext();
const LOCAL_KEY = "myapp_user";
const CART_KEY = "myapp_cart_local";

const BASE_URL =
  Platform.OS === "android" ? "http://192.168.187.112:3000" : "http://localhost:3000";

export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [restored, setRestored] = useState(false);

  //  Restore user ONLY ONCE on app start
  useEffect(() => {
    let isMounted = true;
    
    const restore = async () => {
      try {
        const raw = await AsyncStorage.getItem(LOCAL_KEY);
        if (raw && isMounted) {
          const parsed = JSON.parse(raw);
          console.log("Restored user:", parsed.email);
          setUser(parsed);
        }
      } catch (e) {
        console.warn("Auth restore failed", e);
      } finally {
        if (isMounted) {
          setLoading(false);
          setRestored(true);
        }
      }
    };
    
    restore();
    
    return () => {
      isMounted = false;
    };
  }, []); //

  //  Save user to AsyncStorage
  const saveToStorage = async (userObj) => {
    try {
      await AsyncStorage.setItem(LOCAL_KEY, JSON.stringify(userObj));
      console.log("User saved to storage:", userObj.email);
    } catch (e) {
      console.warn("Failed to save user", e);
    }
  };
  

  //  SignUp (no auto login)
const signUp = async ({ name, email, password }) => {
  const existing = await fetch(`${BASE_URL}/users?email=${encodeURIComponent(email)}`).then(r => r.json());
  if (existing.length) throw new Error("User with this email already exists");

  const res = await fetch(`${BASE_URL}/users`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ name, email, password, cart: [] }),
  }).then(r => r.json());

  

  return res; // just return the created user
};

  //  SignIn
  const signIn = async ({ email, password }) => {
    const found = await fetch(`${BASE_URL}/users?email=${encodeURIComponent(email)}&password=${encodeURIComponent(password)}`).then(r => r.json());
    if (!found.length) throw new Error("Invalid credentials");

    const u = found[0];
    setUser(u);
    await saveToStorage(u);
    return u;
  };

  //  SignOut - Clear user state and storage
  const signOut = async (clearCartFn) => {
    try {
      console.log("=== LOGOUT STARTED ===");
      
      // 1. Clear cart first if provided
      if (clearCartFn) {
        console.log("Clearing cart...");
        await clearCartFn();
      }

      // 2. Clear user state IMMEDIATELY
      console.log("Clearing user state...");
      setUser(null);

      // 3. Clear AsyncStorage
      console.log("Clearing AsyncStorage...");
      await AsyncStorage.multiRemove([LOCAL_KEY, CART_KEY]);
      
      // 4. Verify it's cleared
      const check = await AsyncStorage.getItem(LOCAL_KEY);
      console.log("Storage check after clear:", check);
      
      console.log("=== LOGOUT COMPLETED ===");
    } catch (e) {
      console.error("Sign out failed", e);
    }
  };

  const updateUser = async (updated) => {
    // Don't update if user is null (logged out)
    if (!user) return;
    
    setUser(updated);
    await saveToStorage(updated);
  };

  return (
    <AuthContext.Provider
      value={{
        user,
        loading,
        restored,
        signIn,
        signUp,
        signOut,
        updateUser,
        BASE_URL,
      }}
    >
      {children}
    </AuthContext.Provider>
  );
};


//

//

// src/CartContext.js
import React, { createContext, useContext, useEffect, useState, useRef } from "react";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { AuthContext } from "./AuthContext";

export const CartContext = createContext();
const LOCAL_CART_KEY = "myapp_cart_local";

export const CartProvider = ({ children }) => {
  const { user, BASE_URL, updateUser } = useContext(AuthContext);
  const [cartItems, setCartItems] = useState([]);
  const isClearing = useRef(false);

  // Load cart whenever user changes
  useEffect(() => {
    const init = async () => {
      // Skip if we're in the middle of clearing
      if (isClearing.current) return;

      if (!user) {
        // Load local cart for guest users
        try {
          const raw = await AsyncStorage.getItem(LOCAL_CART_KEY);
          if (raw) setCartItems(JSON.parse(raw));
          else setCartItems([]);
        } catch (e) {
          console.warn("Failed to load local cart", e);
          setCartItems([]);
        }
        return;
      }

      try {
        // Load server cart for logged-in users
        const res = await fetch(`${BASE_URL}/users/${user.id}`).then(r => r.json());
        setCartItems(res.cart || []);
        updateUser(res);
      } catch (e) {
        console.warn("Failed to load server cart", e);
        setCartItems([]);
      }
    };
    init();
  }, [user?.id]); // Only re-run when user ID changes

  const persistCart = async (newCart) => {
    setCartItems(newCart);

    if (user && !isClearing.current) {
      try {
        const updatedUser = await fetch(`${BASE_URL}/users/${user.id}`, {
          method: "PATCH",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ cart: newCart }),
        }).then(r => r.json());
        updateUser(updatedUser);
      } catch (e) {
        console.warn("Failed to sync cart to server", e);
      }
    } else if (!isClearing.current) {
      await AsyncStorage.setItem(LOCAL_CART_KEY, JSON.stringify(newCart));
    }
  };

  const addToCart = (product) => {
    setCartItems(prev => {
      const existing = prev.find(i => i.id === product.id);
      const next = existing
        ? prev.map(i => i.id === product.id ? { ...i, quantity: i.quantity + 1 } : i)
        : [...prev, { ...product, quantity: 1 }];
      persistCart(next);
      return next;
    });
  };

  const increaseQuantity = (id) => {
    setCartItems(prev => {
      const next = prev.map(i => i.id === id ? { ...i, quantity: i.quantity + 1 } : i);
      persistCart(next);
      return next;
    });
  };

  const decreaseQuantity = (id) => {
    setCartItems(prev => {
      const next = prev
        .map(i => i.id === id ? { ...i, quantity: Math.max(i.quantity - 1, 0) } : i)
        .filter(i => i.quantity > 0);
      persistCart(next);
      return next;
    });
  };

  const removeFromCart = (id) => {
    setCartItems(prev => {
      const next = prev.filter(i => i.id !== id);
      persistCart(next);
      return next;
    });
  };

  const clearCart = async () => {
    isClearing.current = true;
    setCartItems([]);
    try {
      await AsyncStorage.removeItem(LOCAL_CART_KEY);
    } catch (e) {
      console.warn("Failed to clear cart", e);
    }
    isClearing.current = false;
  };

  return (
    <CartContext.Provider
      value={{
        cartItems,
        addToCart,
        increaseQuantity,
        decreaseQuantity,
        removeFromCart,
        clearCart,
      }}
    >
      {children}
    </CartContext.Provider>
  );
};

//

//

//


// src/screens/LoginScreen.js
import React, { useContext, useEffect, useState } from "react";
import {
  View,
  StyleSheet,
  Alert,
  ImageBackground,
  KeyboardAvoidingView,
  Platform,
  Keyboard,
  TouchableWithoutFeedback,
} from "react-native";
import { TextInput, Button, Text } from "react-native-paper";
import { Formik } from "formik";
import * as Yup from "yup";
import { AuthContext } from "../AuthContext";
import Icon from "react-native-vector-icons/MaterialIcons";

export default function LoginScreen({ navigation }) {
  const { signIn, user, restored } = useContext(AuthContext);
  const [showPassword, setShowPassword] = useState(false);

  useEffect(() => {
    if (restored && user) {
      navigation.reset({
        index: 0,
        routes: [{ name: "MyDrawer" }],
      });
    }
  }, [user, restored]);

  const LoginSchema = Yup.object().shape({
    email: Yup.string().email("Invalid email").required("Email required"),
    password: Yup.string().min(4, "Too short").required("Password required"),
  });

  return (
    <ImageBackground
      source={{
        uri: "https://plus.unsplash.com/premium_photo-1672082518036-92db94a85341?q=80&w=327&auto=format&fit=crop",
      }}
      style={styles.background}
      resizeMode="cover"
    >
      <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
        <KeyboardAvoidingView
          style={styles.container}
          behavior={Platform.OS === "ios" ? "padding" : undefined}
        >
          <Formik
            initialValues={{ email: "", password: "" }}
            validationSchema={LoginSchema}
            onSubmit={async (values, { setSubmitting }) => {
              try {
                await signIn(values);
                navigation.reset({
                  index: 0,
                  routes: [{ name: "MyDrawer" }],
                });
              } catch (e) {
                Alert.alert("Login failed", e.message || "Check credentials");
              } finally {
                setSubmitting(false);
              }
            }}
          >
            {({
              handleChange,
              handleBlur,
              handleSubmit,
              values,
              errors,
              touched,
              isSubmitting,
            }) => {
              const isFormFilled = values.email && values.password;

              return (
                <View style={styles.formContainer}>
                  <Text style={styles.h}>Infinity Store</Text>

                  <TextInput
                    label="Email"
                    mode="outlined"
                    value={values.email}
                    onChangeText={handleChange("email")}
                    onBlur={handleBlur("email")}
                    autoCapitalize="none"
                    style={styles.input}
                    error={touched.email && !!errors.email}
                    left={<TextInput.Icon icon={() => <Icon name="email" size={20} />} />}
                  />
                  {touched.email && errors.email && (
                    <Text style={styles.errorText}>{errors.email}</Text>
                  )}

                  <TextInput
                    label="Password"
                    mode="outlined"
                    value={values.password}
                    onChangeText={handleChange("password")}
                    onBlur={handleBlur("password")}
                    secureTextEntry={!showPassword}
                    style={styles.input}
                    error={touched.password && !!errors.password}
                    left={<TextInput.Icon icon={() => <Icon name="lock" size={20} />} />}
                    right={
                      <TextInput.Icon
                        icon={() => (
                          <Icon
                            name={showPassword ? "visibility" : "visibility-off"}
                            size={20}
                          />
                        )}
                        onPress={() => setShowPassword(!showPassword)}
                      />
                    }
                  />
                  {touched.password && errors.password && (
                    <Text style={styles.errorText}>{errors.password}</Text>
                  )}

                  <Button
                    mode="contained"
                    onPress={handleSubmit}
                    loading={isSubmitting}
                    disabled={!isFormFilled || isSubmitting}
                    style={styles.loginBtn}
                    buttonColor="orange"
                    textColor="white"
                  >
                    {isSubmitting ? "Logging in..." : "Login"}
                  </Button>

                  <Button
                    mode="text"
                    onPress={() => navigation.navigate("Signup")}
                    textColor="gray"
                  >
                    Create Account
                  </Button>
                </View>
              );
            }}
          </Formik>
        </KeyboardAvoidingView>
      </TouchableWithoutFeedback>
    </ImageBackground>
  );
}

const styles = StyleSheet.create({
  background: { flex: 1 },
  container: { flex: 1, justifyContent: "center", padding: 20 },
  formContainer: {
    backgroundColor: "rgba(255,255,255,0.85)",
    padding: 20,
    borderRadius: 12,
  },
  input: { marginBottom: 10 },
  h: {
    fontSize: 35,
    fontStyle: "italic",
    fontWeight: "700",
    marginBottom: 20,
    textAlign: "center",
  },
  errorText: { color: "red", marginBottom: 8, marginLeft: 5 },
  loginBtn: { marginTop: 10, marginBottom: 10, borderRadius: 8 },
});


//

//

//src/screens/SignupScreen.js

// SignupScreen.js  (updated)
import React, { useContext, useState } from "react";
import {
  View,
  StyleSheet,
  ImageBackground,
  KeyboardAvoidingView,
  Platform,
  Modal,
  Animated,
  Keyboard,
  TouchableWithoutFeedback,
  Alert,
} from "react-native";
import { TextInput, Button, Text } from "react-native-paper";
import { Formik } from "formik";
import * as Yup from "yup";
import { AuthContext } from "../AuthContext";
import Icon from "react-native-vector-icons/MaterialIcons";

export default function SignupScreen({ navigation }) {
  const { signUp } = useContext(AuthContext);
  const [showPassword, setShowPassword] = useState(false);
  const [showConfirm, setShowConfirm] = useState(false);
  const [showModal, setShowModal] = useState(false);
  const [serverError, setServerError] = useState(null);
  const fadeAnim = useState(new Animated.Value(0))[0]; // for animation

  const SignupSchema = Yup.object().shape({
    name: Yup.string().required("Name required"),
    email: Yup.string().email("Invalid email").required("Email required"),
    password: Yup.string().min(4, "Too short").required("Password required"),
    confirmPassword: Yup.string()
      .oneOf([Yup.ref("password")], "Passwords must match")
      .required("Confirm your password"),
  });

  const showSuccessModal = () => {
    setShowModal(true);
    Animated.timing(fadeAnim, {
      toValue: 1,
      duration: 400,
      useNativeDriver: true,
    }).start();

    setTimeout(() => {
      Animated.timing(fadeAnim, {
        toValue: 0,
        duration: 300,
        useNativeDriver: true,
      }).start(() => {
        setShowModal(false);
        navigation.navigate("Login");
      });
    }, 2000); // slightly shorter delay
  };

  return (
    <ImageBackground
      source={{
        uri:
          "https://plus.unsplash.com/premium_photo-1672082518036-92db94a85341?q=80&w=327&auto=format&fit=crop",
      }}
      style={styles.background}
      resizeMode="cover"
    >
      <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
        <View style={{ flex: 1 }}>
          <KeyboardAvoidingView
            style={styles.container}
            behavior={Platform.OS === "ios" ? "padding" : undefined}
          >
            <Formik
              initialValues={{
                name: "",
                email: "",
                password: "",
                confirmPassword: "",
              }}
              validationSchema={SignupSchema}
              onSubmit={async (values, { setSubmitting, resetForm }) => {
                setServerError(null);
                try {
                  await signUp({
                    name: values.name,
                    email: values.email,
                    password: values.password,
                  });
                  resetForm();
                  showSuccessModal(); // show animated popup
                } catch (e) {
                  // show error properly
                  const msg = e?.message || "Something went wrong";
                  setServerError(msg);
                  Alert.alert("Signup failed", msg);
                } finally {
                  setSubmitting(false);
                }
              }}
            >
              {({
                handleChange,
                handleBlur,
                handleSubmit,
                values,
                errors,
                touched,
                isSubmitting,
              }) => {
                const isFormFilled =
                  values.name && values.email && values.password && values.confirmPassword;

                return (
                  <View style={styles.formContainer}>
                    <Text style={styles.h}>Infinity Store</Text>

                    <TextInput
                      label="Name"
                      mode="outlined"
                      value={values.name}
                      onChangeText={handleChange("name")}
                      onBlur={handleBlur("name")}
                      style={styles.input}
                      error={touched.name && !!errors.name}
                      left={<TextInput.Icon icon={() => <Icon name="person" size={20} />} />}
                    />
                    {touched.name && errors.name && (
                      <Text style={styles.errorText}>{errors.name}</Text>
                    )}

                    <TextInput
                      label="Email"
                      mode="outlined"
                      value={values.email}
                      onChangeText={handleChange("email")}
                      onBlur={handleBlur("email")}
                      autoCapitalize="none"
                      style={styles.input}
                      error={touched.email && !!errors.email}
                      left={<TextInput.Icon icon={() => <Icon name="email" size={20} />} />}
                    />
                    {touched.email && errors.email && (
                      <Text style={styles.errorText}>{errors.email}</Text>
                    )}

                    {/* Password Field with Eye Toggle */}
                    <TextInput
                      label="Password"
                      mode="outlined"
                      value={values.password}
                      onChangeText={handleChange("password")}
                      onBlur={handleBlur("password")}
                      secureTextEntry={!showPassword}
                      style={styles.input}
                      error={touched.password && !!errors.password}
                      left={<TextInput.Icon icon={() => <Icon name="lock" size={20} />} />}
                      right={
                        <TextInput.Icon
                          icon={() => (
                            <Icon name={showPassword ? "visibility" : "visibility-off"} size={20} />
                          )}
                          onPress={() => setShowPassword(!showPassword)}
                        />
                      }
                    />
                    {touched.password && errors.password && (
                      <Text style={styles.errorText}>{errors.password}</Text>
                    )}

                    {/* Confirm Password Field */}
                    <TextInput
                      label="Confirm Password"
                      mode="outlined"
                      value={values.confirmPassword}
                      onChangeText={handleChange("confirmPassword")}
                      onBlur={handleBlur("confirmPassword")}
                      secureTextEntry={!showConfirm}
                      style={styles.input}
                      error={touched.confirmPassword && !!errors.confirmPassword}
                      left={<TextInput.Icon icon={() => <Icon name="lock-outline" size={20} />} />}
                      right={
                        <TextInput.Icon
                          icon={() => (
                            <Icon name={showConfirm ? "visibility" : "visibility-off"} size={20} />
                          )}
                          onPress={() => setShowConfirm(!showConfirm)}
                        />
                      }
                    />
                    {touched.confirmPassword && errors.confirmPassword && (
                      <Text style={styles.errorText}>{errors.confirmPassword}</Text>
                    )}

                    {/* inline server error */}
                    {serverError ? <Text style={styles.serverError}>{serverError}</Text> : null}

                    <Button
                      mode="contained"
                      onPress={handleSubmit}
                      loading={isSubmitting}
                      disabled={!isFormFilled || isSubmitting}
                      style={styles.signupBtn}
                      buttonColor="orange"
                      textColor="white"
                    >
                      {isSubmitting ? "Signing up..." : "Sign Up"}
                    </Button>

                    <Button mode="text" onPress={() => navigation.goBack()} textColor="gray">
                      Back to Login
                    </Button>
                  </View>
                );
              }}
            </Formik>
          </KeyboardAvoidingView>

          {/*  Animated Success Modal */}
          <Modal visible={showModal} transparent animationType="fade">
            <View style={styles.modalOverlay}>
              <Animated.View style={[styles.modalCard, { opacity: fadeAnim }]}>
                <Icon name="check-circle" size={60} color="green" />
                <Text style={styles.modalText}>Registered Successfully!</Text>
                <Text style={styles.modalSub}>Redirecting to Login...</Text>
              </Animated.View>
            </View>
          </Modal>
        </View>
      </TouchableWithoutFeedback>
    </ImageBackground>
  );
}

const styles = StyleSheet.create({
  background: { flex: 1 },
  container: { flex: 1, justifyContent: "center", padding: 20 },
  formContainer: {
    backgroundColor: "rgba(255,255,255,0.85)",
    padding: 20,
    borderRadius: 12,
  },
  input: { marginBottom: 10 },
  h: {
    fontSize: 35,
    fontStyle: "italic",
    fontWeight: "700",
    marginBottom: 20,
    textAlign: "center",
  },
  errorText: { color: "red", marginBottom: 8, marginLeft: 5 },
  serverError: { color: "red", marginBottom: 8, marginLeft: 5, textAlign: "center" },
  signupBtn: { marginTop: 10, marginBottom: 10, borderRadius: 8 },

  // Modal styles
  modalOverlay: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: "rgba(0,0,0,0.4)",
  },
  modalCard: {
    width: "80%",
    backgroundColor: "white",
    borderRadius: 15,
    padding: 25,
    alignItems: "center",
    elevation: 5,
  },
  modalText: {
    fontSize: 20,
    fontWeight: "bold",
    marginTop: 10,
    color: "green",
  },
  modalSub: {
    fontSize: 14,
    color: "gray",
    marginTop: 5,
  },
});


//npm command for dependency

npm install @react-native-async-storage/async-storage@^2.2.0 \
@react-native/new-app-screen@0.81.4 \
@react-navigation/bottom-tabs@^7.4.7 \
@react-navigation/drawer@^7.5.8 \
@react-navigation/native@^7.1.17 \
@react-navigation/stack@^7.4.8 \
express@^5.1.0 \
formik@^2.4.6 \
react@19.1.0 \
react-native@0.81.4 \
react-native-dropdown-picker@^5.4.6 \
react-native-gesture-handler@^2.28.0 \
react-native-image-picker@^8.2.1 \
react-native-paper@^5.14.5 \
react-native-reanimated@^4.1.2 \
react-native-safe-area-context@^5.6.1 \
react-native-screens@^4.16.0 \
react-native-vector-icons@^10.3.0 \
react-native-worklets@^0.6.0 \
yup@^1.7.1

//

//ipconfig
//npx json-server db.json


About the Author

john

Administrator

Visit Website View All Posts

Continue Reading

Previous: Top 10 Web Browsers in the World
Next: weather app build with latest technology

Related Stories

  • Uncategorized
  • React Native

How To Fix Gradle Error In React Native

john November 2, 2025
  • React Native

Top 20 Most Asked React Native Interview Questions in 2025 (With Answers)

john August 19, 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.