diff --git a/app.json b/app.json
index 2fd62b4..93fa4bc 100644
--- a/app.json
+++ b/app.json
@@ -33,7 +33,8 @@
"resizeMode": "contain",
"backgroundColor": "#ffffff"
}
- ]
+ ],
+ "expo-secure-store"
],
"experiments": {
"typedRoutes": true
diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx
index e6b977c..084ecca 100644
--- a/app/(tabs)/_layout.tsx
+++ b/app/(tabs)/_layout.tsx
@@ -1,7 +1,15 @@
import { Ionicons, MaterialCommunityIcons } from "@expo/vector-icons";
-import { Tabs } from "expo-router";
+import { Tabs, Redirect } from "expo-router";
+import { useAuth } from ".././context/auth-context";
export default function TabLayout() {
+ const { isAuthenticated, isLoading } = useAuth();
+
+ // If not authenticated and not loading, redirect to welcome
+ if (!isLoading && !isAuthenticated) {
+ return ;
+ }
+
return (
(
diff --git a/app/(tabs)/index.tsx b/app/(tabs)/home.tsx
similarity index 95%
rename from app/(tabs)/index.tsx
rename to app/(tabs)/home.tsx
index 072d868..f587fb9 100644
--- a/app/(tabs)/index.tsx
+++ b/app/(tabs)/home.tsx
@@ -52,7 +52,7 @@ const foodHighlights = [
];
const navigateToFoodDetail = (foodId: string) => {
- router.push({ pathname: "/food/[id]", params: { id: foodId } });
+ router.push({ pathname: "/recipe-detail", params: { id: foodId } });
};
export default function HomeScreen() {
@@ -217,14 +217,11 @@ export default function HomeScreen() {
-
- {/* Food Highlights Section */}
-
-
-
- Food Highlights
-
-
+ {/* Highlights Section */}
+
+
+ Highlights
+
{foodHighlights.map((food) => (
diff --git a/app/+not-found.tsx b/app/+not-found.tsx
index 215b0ed..f4b2a33 100644
--- a/app/+not-found.tsx
+++ b/app/+not-found.tsx
@@ -10,7 +10,7 @@ export default function NotFoundScreen() {
This screen does not exist.
-
+
Go to home screen!
diff --git a/app/_layout.tsx b/app/_layout.tsx
index 79068cd..a979f52 100644
--- a/app/_layout.tsx
+++ b/app/_layout.tsx
@@ -1,10 +1,12 @@
import { Stack } from "expo-router";
import { GestureHandlerRootView } from "react-native-gesture-handler";
+import { AuthProvider } from "./context/auth-context";
import "../global.css";
export default function RootLayout() {
return (
+
+
);
}
diff --git a/app/context/auth-context.tsx b/app/context/auth-context.tsx
new file mode 100644
index 0000000..4173068
--- /dev/null
+++ b/app/context/auth-context.tsx
@@ -0,0 +1,111 @@
+import React, { createContext, useState, useContext, useEffect } from 'react';
+import * as SecureStore from 'expo-secure-store';
+import { router } from 'expo-router';
+
+type AuthContextType = {
+ isAuthenticated: boolean;
+ isLoading: boolean;
+ login: (email: string, password: string) => Promise;
+ signup: (name: string, email: string, password: string) => Promise;
+ logout: () => Promise;
+};
+
+const AuthContext = createContext(null);
+
+export function AuthProvider({ children }: { children: React.ReactNode }) {
+ const [authState, setAuthState] = useState({
+ isAuthenticated: false,
+ isLoading: true
+ });
+
+ // Use a single useEffect to check authentication status only once on mount
+ useEffect(() => {
+ // Check if user is logged in on app start
+ async function loadToken() {
+ try {
+ const token = await SecureStore.getItemAsync('userToken');
+ // Update state only once with both values
+ setAuthState({
+ isAuthenticated: !!token,
+ isLoading: false
+ });
+ } catch (error) {
+ console.log('Error loading token:', error);
+ setAuthState({
+ isAuthenticated: false,
+ isLoading: false
+ });
+ }
+ }
+
+ loadToken();
+ }, []); // Empty dependency array ensures this runs only once
+
+ const login = async (email: string, password: string) => {
+ try {
+ // In a real app, you would validate credentials with a backend
+ await SecureStore.setItemAsync('userToken', 'dummy-auth-token');
+ setAuthState({
+ ...authState,
+ isAuthenticated: true
+ });
+ // Redirect to home tab specifically
+ router.replace('../(tabs)/home');
+ } catch (error) {
+ console.error('Login error:', error);
+ throw error;
+ }
+ };
+
+ const signup = async (name: string, email: string, password: string) => {
+ try {
+ // In a real app, you would register the user with a backend
+ await SecureStore.setItemAsync('userToken', 'dummy-auth-token');
+ setAuthState({
+ ...authState,
+ isAuthenticated: true
+ });
+ // Redirect to home tab specifically
+ router.replace('./(tabs)/home');
+ } catch (error) {
+ console.error('Signup error:', error);
+ throw error;
+ }
+ };
+
+ const logout = async () => {
+ try {
+ await SecureStore.deleteItemAsync('userToken');
+ setAuthState({
+ ...authState,
+ isAuthenticated: false
+ });
+ router.replace('/');
+ } catch (error) {
+ console.error('Logout error:', error);
+ throw error;
+ }
+ };
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useAuth() {
+ const context = useContext(AuthContext);
+ if (!context) {
+ throw new Error('useAuth must be used within an AuthProvider');
+ }
+ return context;
+}
\ No newline at end of file
diff --git a/app/index.tsx b/app/index.tsx
new file mode 100644
index 0000000..7769c7d
--- /dev/null
+++ b/app/index.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import { View, ActivityIndicator } from 'react-native';
+import { Redirect } from 'expo-router';
+import { useAuth } from './context/auth-context';
+
+export default function Index() {
+ const { isAuthenticated, isLoading } = useAuth();
+
+ // Show loading indicator while checking auth status
+ if (isLoading) {
+ return (
+
+
+
+ );
+ }
+
+ // Redirect based on authentication status
+ if (isAuthenticated) {
+ return ;
+ } else {
+ return ;
+ }
+}
\ No newline at end of file
diff --git a/app/login.tsx b/app/login.tsx
new file mode 100644
index 0000000..d87cae0
--- /dev/null
+++ b/app/login.tsx
@@ -0,0 +1,106 @@
+import React, { useState } from 'react';
+import { View, Text, TextInput, TouchableOpacity, SafeAreaView, StatusBar, Alert } from 'react-native';
+import { router } from 'expo-router';
+import { Feather } from '@expo/vector-icons';
+import { useAuth } from './context/auth-context';
+
+export default function LoginScreen() {
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [showPassword, setShowPassword] = useState(false);
+ const [isLoading, setIsLoading] = useState(false);
+
+ const { login } = useAuth();
+
+ const handleLogin = async () => {
+ if (!email || !password) {
+ Alert.alert('Error', 'Please fill in all fields');
+ return;
+ }
+
+ try {
+ setIsLoading(true);
+ await login(email, password);
+ } catch (error) {
+ Alert.alert('Error', 'Failed to login. Please try again.');
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ return (
+
+
+
+
+ {/* Back Button */}
+ router.back()}
+ >
+
+
+
+ {/* Header */}
+ Login to your account
+
+ {/* Form */}
+
+
+ Email
+
+
+
+
+ Password
+
+
+ setShowPassword(!showPassword)}
+ >
+
+
+
+
+
+
+ Forgot Password?
+
+
+
+
+ {isLoading ? 'Logging in...' : 'Login'}
+
+
+
+
+ {/* Sign Up Link */}
+
+ Don't have an account?
+ router.push('/signup')}>
+ Sign Up
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/app/signup.tsx b/app/signup.tsx
new file mode 100644
index 0000000..e860e13
--- /dev/null
+++ b/app/signup.tsx
@@ -0,0 +1,141 @@
+import React, { useState } from 'react';
+import { View, Text, TextInput, TouchableOpacity, SafeAreaView, StatusBar, Alert, ScrollView } from 'react-native';
+import { router } from 'expo-router';
+import { Feather } from '@expo/vector-icons';
+import { useAuth } from './context/auth-context';
+
+export default function SignupScreen() {
+ const [name, setName] = useState('');
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [confirmPassword, setConfirmPassword] = useState('');
+ const [showPassword, setShowPassword] = useState(false);
+ const [isLoading, setIsLoading] = useState(false);
+
+ const { signup } = useAuth();
+
+ const handleSignup = async () => {
+ if (!name || !email || !password || !confirmPassword) {
+ Alert.alert('Error', 'Please fill in all fields');
+ return;
+ }
+
+ if (password !== confirmPassword) {
+ Alert.alert('Error', 'Passwords do not match');
+ return;
+ }
+
+ try {
+ setIsLoading(true);
+ await signup(name, email, password);
+ } catch (error) {
+ Alert.alert('Error', 'Failed to sign up. Please try again.');
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ return (
+
+
+
+
+
+ {/* Back Button */}
+ router.back()}
+ >
+
+
+
+ {/* Header */}
+ Create an account
+
+ {/* Form */}
+
+
+ Full Name
+
+
+
+
+ Email
+
+
+
+
+ Password
+
+
+ setShowPassword(!showPassword)}
+ >
+
+
+
+
+
+
+ Confirm Password
+
+
+ setShowPassword(!showPassword)}
+ >
+
+
+
+
+
+
+
+ {isLoading ? 'Signing up...' : 'Sign Up'}
+
+
+
+
+ {/* Login Link */}
+
+ Already have an account?
+ router.push('/login')}>
+ Login
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/app/welcome.tsx b/app/welcome.tsx
new file mode 100644
index 0000000..716e445
--- /dev/null
+++ b/app/welcome.tsx
@@ -0,0 +1,51 @@
+import React from 'react';
+import { View, Text, Image, TouchableOpacity, SafeAreaView, StatusBar } from 'react-native';
+import { router } from 'expo-router';
+import { Feather } from '@expo/vector-icons';
+
+export default function WelcomeScreen() {
+ return (
+
+
+
+
+ {/* Logo and Welcome Text */}
+
+
+
+
+
+ Welcome to ChefHai
+
+ Discover, cook and share delicious recipes with food lovers around the world
+
+
+
+ {/* Food Image */}
+
+
+
+
+ {/* Buttons */}
+
+ router.push('/login')}
+ >
+ Login
+
+
+ router.push('/signup')}
+ >
+ Sign Up
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index a73cf83..726325b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -22,6 +22,7 @@
"expo-image-picker": "~16.1.4",
"expo-linking": "~7.1.4",
"expo-router": "~5.0.6",
+ "expo-secure-store": "~14.2.3",
"expo-splash-screen": "~0.30.8",
"expo-status-bar": "~2.2.3",
"expo-symbols": "~0.4.4",
@@ -6439,6 +6440,15 @@
"node": ">=10"
}
},
+ "node_modules/expo-secure-store": {
+ "version": "14.2.3",
+ "resolved": "https://registry.npmjs.org/expo-secure-store/-/expo-secure-store-14.2.3.tgz",
+ "integrity": "sha512-hYBbaAD70asKTFd/eZBKVu+9RTo9OSTMMLqXtzDF8ndUGjpc6tmRCoZtrMHlUo7qLtwL5jm+vpYVBWI8hxh/1Q==",
+ "license": "MIT",
+ "peerDependencies": {
+ "expo": "*"
+ }
+ },
"node_modules/expo-splash-screen": {
"version": "0.30.8",
"resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-0.30.8.tgz",
diff --git a/package.json b/package.json
index bd73d78..72dc3d1 100644
--- a/package.json
+++ b/package.json
@@ -40,7 +40,8 @@
"react-native-screens": "~4.10.0",
"react-native-web": "~0.20.0",
"react-native-webview": "13.13.5",
- "tailwindcss": "^3.4.17"
+ "tailwindcss": "^3.4.17",
+ "expo-secure-store": "~14.2.3"
},
"devDependencies": {
"@babel/core": "^7.25.2",