mirror of
https://github.com/Sosokker/chefhai.git
synced 2025-12-19 14:04:08 +01:00
resolve conflict
This commit is contained in:
parent
2b7973ab81
commit
42134d9ecb
3
app.json
3
app.json
@ -33,7 +33,8 @@
|
|||||||
"resizeMode": "contain",
|
"resizeMode": "contain",
|
||||||
"backgroundColor": "#ffffff"
|
"backgroundColor": "#ffffff"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"expo-secure-store"
|
||||||
],
|
],
|
||||||
"experiments": {
|
"experiments": {
|
||||||
"typedRoutes": true
|
"typedRoutes": true
|
||||||
|
|||||||
@ -1,7 +1,15 @@
|
|||||||
import { Ionicons, MaterialCommunityIcons } from "@expo/vector-icons";
|
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() {
|
export default function TabLayout() {
|
||||||
|
const { isAuthenticated, isLoading } = useAuth();
|
||||||
|
|
||||||
|
// If not authenticated and not loading, redirect to welcome
|
||||||
|
if (!isLoading && !isAuthenticated) {
|
||||||
|
return <Redirect href="/welcome" />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs
|
<Tabs
|
||||||
screenOptions={{
|
screenOptions={{
|
||||||
@ -28,7 +36,7 @@ export default function TabLayout() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Tabs.Screen
|
<Tabs.Screen
|
||||||
name="index"
|
name="home"
|
||||||
options={{
|
options={{
|
||||||
title: "Home",
|
title: "Home",
|
||||||
tabBarIcon: ({ color }) => (
|
tabBarIcon: ({ color }) => (
|
||||||
|
|||||||
@ -52,7 +52,7 @@ const foodHighlights = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const navigateToFoodDetail = (foodId: string) => {
|
const navigateToFoodDetail = (foodId: string) => {
|
||||||
router.push({ pathname: "/food/[id]", params: { id: foodId } });
|
router.push({ pathname: "/recipe-detail", params: { id: foodId } });
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function HomeScreen() {
|
export default function HomeScreen() {
|
||||||
@ -217,14 +217,11 @@ export default function HomeScreen() {
|
|||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
{/* Highlights Section */}
|
||||||
{/* Food Highlights Section */}
|
<View className="px-6 mb-6">
|
||||||
<View className="mx-4 mb-6">
|
<View className="flex-row items-center mb-4">
|
||||||
<View className="flex-row items-center mb-3">
|
<Text className="text-2xl font-bold mr-2">Highlights</Text>
|
||||||
<Text className="text-lg font-bold text-[#333] mr-2">
|
<FontAwesome name="star" size={20} color="#ffd60a" />
|
||||||
Food Highlights
|
|
||||||
</Text>
|
|
||||||
<IconSymbol name="star.fill" size={16} color="#FFCC00" />
|
|
||||||
</View>
|
</View>
|
||||||
<View className="w-full">
|
<View className="w-full">
|
||||||
{foodHighlights.map((food) => (
|
{foodHighlights.map((food) => (
|
||||||
@ -10,7 +10,7 @@ export default function NotFoundScreen() {
|
|||||||
<Stack.Screen options={{ title: 'Oops!' }} />
|
<Stack.Screen options={{ title: 'Oops!' }} />
|
||||||
<ThemedView style={styles.container}>
|
<ThemedView style={styles.container}>
|
||||||
<ThemedText type="title">This screen does not exist.</ThemedText>
|
<ThemedText type="title">This screen does not exist.</ThemedText>
|
||||||
<Link href="/" style={styles.link}>
|
<Link href="/welcome" style={styles.link}>
|
||||||
<ThemedText type="link">Go to home screen!</ThemedText>
|
<ThemedText type="link">Go to home screen!</ThemedText>
|
||||||
</Link>
|
</Link>
|
||||||
</ThemedView>
|
</ThemedView>
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
import { Stack } from "expo-router";
|
import { Stack } from "expo-router";
|
||||||
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
||||||
|
import { AuthProvider } from "./context/auth-context";
|
||||||
import "../global.css";
|
import "../global.css";
|
||||||
|
|
||||||
export default function RootLayout() {
|
export default function RootLayout() {
|
||||||
return (
|
return (
|
||||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||||
|
<AuthProvider>
|
||||||
<Stack screenOptions={{ headerShown: false }}>
|
<Stack screenOptions={{ headerShown: false }}>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="(tabs)"
|
name="(tabs)"
|
||||||
@ -20,6 +22,7 @@ export default function RootLayout() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</AuthProvider>
|
||||||
</GestureHandlerRootView>
|
</GestureHandlerRootView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
111
app/context/auth-context.tsx
Normal file
111
app/context/auth-context.tsx
Normal file
@ -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<void>;
|
||||||
|
signup: (name: string, email: string, password: string) => Promise<void>;
|
||||||
|
logout: () => Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const AuthContext = createContext<AuthContextType | null>(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 (
|
||||||
|
<AuthContext.Provider
|
||||||
|
value={{
|
||||||
|
isAuthenticated: authState.isAuthenticated,
|
||||||
|
isLoading: authState.isLoading,
|
||||||
|
login,
|
||||||
|
signup,
|
||||||
|
logout
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</AuthContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAuth() {
|
||||||
|
const context = useContext(AuthContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useAuth must be used within an AuthProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
24
app/index.tsx
Normal file
24
app/index.tsx
Normal file
@ -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 (
|
||||||
|
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center', backgroundColor: 'white' }}>
|
||||||
|
<ActivityIndicator size="large" color="#ffd60a" />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect based on authentication status
|
||||||
|
if (isAuthenticated) {
|
||||||
|
return <Redirect href="/welcome" />;
|
||||||
|
} else {
|
||||||
|
return <Redirect href="/welcome" />;
|
||||||
|
}
|
||||||
|
}
|
||||||
106
app/login.tsx
Normal file
106
app/login.tsx
Normal file
@ -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 (
|
||||||
|
<SafeAreaView className="flex-1 bg-white">
|
||||||
|
<StatusBar barStyle="dark-content" />
|
||||||
|
|
||||||
|
<View className="px-6 py-10 flex-1">
|
||||||
|
{/* Back Button */}
|
||||||
|
<TouchableOpacity
|
||||||
|
className="bg-[#ffd60a] p-3 rounded-lg w-12 mb-8"
|
||||||
|
onPress={() => router.back()}
|
||||||
|
>
|
||||||
|
<Feather name="arrow-left" size={24} color="#bb0718" />
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
{/* Header */}
|
||||||
|
<Text className="text-3xl font-bold mb-8">Login to your account</Text>
|
||||||
|
|
||||||
|
{/* Form */}
|
||||||
|
<View className="space-y-6">
|
||||||
|
<View>
|
||||||
|
<Text className="text-gray-700 mb-2 font-medium">Email</Text>
|
||||||
|
<TextInput
|
||||||
|
className="bg-gray-100 py-4 px-4 rounded-xl"
|
||||||
|
placeholder="Enter your email"
|
||||||
|
value={email}
|
||||||
|
onChangeText={setEmail}
|
||||||
|
keyboardType="email-address"
|
||||||
|
autoCapitalize="none"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View>
|
||||||
|
<Text className="text-gray-700 mb-2 font-medium">Password</Text>
|
||||||
|
<View className="flex-row items-center bg-gray-100 rounded-xl">
|
||||||
|
<TextInput
|
||||||
|
className="flex-1 py-4 px-4"
|
||||||
|
placeholder="Enter your password"
|
||||||
|
value={password}
|
||||||
|
onChangeText={setPassword}
|
||||||
|
secureTextEntry={!showPassword}
|
||||||
|
autoCapitalize="none"
|
||||||
|
/>
|
||||||
|
<TouchableOpacity
|
||||||
|
className="pr-4"
|
||||||
|
onPress={() => setShowPassword(!showPassword)}
|
||||||
|
>
|
||||||
|
<Feather name={showPassword ? "eye-off" : "eye"} size={20} color="gray" />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<TouchableOpacity>
|
||||||
|
<Text className="text-right text-[#bb0718] font-medium">Forgot Password?</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
className="bg-[#ffd60a] py-4 rounded-xl mt-6"
|
||||||
|
onPress={handleLogin}
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
<Text className="text-center font-bold text-lg">
|
||||||
|
{isLoading ? 'Logging in...' : 'Login'}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Sign Up Link */}
|
||||||
|
<View className="flex-row justify-center mt-8">
|
||||||
|
<Text className="text-gray-600">Don't have an account? </Text>
|
||||||
|
<TouchableOpacity onPress={() => router.push('/signup')}>
|
||||||
|
<Text className="text-[#bb0718] font-medium">Sign Up</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
141
app/signup.tsx
Normal file
141
app/signup.tsx
Normal file
@ -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 (
|
||||||
|
<SafeAreaView className="flex-1 bg-white">
|
||||||
|
<StatusBar barStyle="dark-content" />
|
||||||
|
|
||||||
|
<ScrollView className="flex-1">
|
||||||
|
<View className="px-6 py-10">
|
||||||
|
{/* Back Button */}
|
||||||
|
<TouchableOpacity
|
||||||
|
className="bg-[#ffd60a] p-3 rounded-lg w-12 mb-8"
|
||||||
|
onPress={() => router.back()}
|
||||||
|
>
|
||||||
|
<Feather name="arrow-left" size={24} color="#bb0718" />
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
{/* Header */}
|
||||||
|
<Text className="text-3xl font-bold mb-8">Create an account</Text>
|
||||||
|
|
||||||
|
{/* Form */}
|
||||||
|
<View className="space-y-6">
|
||||||
|
<View>
|
||||||
|
<Text className="text-gray-700 mb-2 font-medium">Full Name</Text>
|
||||||
|
<TextInput
|
||||||
|
className="bg-gray-100 py-4 px-4 rounded-xl"
|
||||||
|
placeholder="Enter your full name"
|
||||||
|
value={name}
|
||||||
|
onChangeText={setName}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View>
|
||||||
|
<Text className="text-gray-700 mb-2 font-medium">Email</Text>
|
||||||
|
<TextInput
|
||||||
|
className="bg-gray-100 py-4 px-4 rounded-xl"
|
||||||
|
placeholder="Enter your email"
|
||||||
|
value={email}
|
||||||
|
onChangeText={setEmail}
|
||||||
|
keyboardType="email-address"
|
||||||
|
autoCapitalize="none"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View>
|
||||||
|
<Text className="text-gray-700 mb-2 font-medium">Password</Text>
|
||||||
|
<View className="flex-row items-center bg-gray-100 rounded-xl">
|
||||||
|
<TextInput
|
||||||
|
className="flex-1 py-4 px-4"
|
||||||
|
placeholder="Enter your password"
|
||||||
|
value={password}
|
||||||
|
onChangeText={setPassword}
|
||||||
|
secureTextEntry={!showPassword}
|
||||||
|
autoCapitalize="none"
|
||||||
|
/>
|
||||||
|
<TouchableOpacity
|
||||||
|
className="pr-4"
|
||||||
|
onPress={() => setShowPassword(!showPassword)}
|
||||||
|
>
|
||||||
|
<Feather name={showPassword ? "eye-off" : "eye"} size={20} color="gray" />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View>
|
||||||
|
<Text className="text-gray-700 mb-2 font-medium">Confirm Password</Text>
|
||||||
|
<View className="flex-row items-center bg-gray-100 rounded-xl">
|
||||||
|
<TextInput
|
||||||
|
className="flex-1 py-4 px-4"
|
||||||
|
placeholder="Confirm your password"
|
||||||
|
value={confirmPassword}
|
||||||
|
onChangeText={setConfirmPassword}
|
||||||
|
secureTextEntry={!showPassword}
|
||||||
|
autoCapitalize="none"
|
||||||
|
/>
|
||||||
|
<TouchableOpacity
|
||||||
|
className="pr-4"
|
||||||
|
onPress={() => setShowPassword(!showPassword)}
|
||||||
|
>
|
||||||
|
<Feather name={showPassword ? "eye-off" : "eye"} size={20} color="gray" />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
className="bg-[#ffd60a] py-4 rounded-xl mt-6"
|
||||||
|
onPress={handleSignup}
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
<Text className="text-center font-bold text-lg">
|
||||||
|
{isLoading ? 'Signing up...' : 'Sign Up'}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Login Link */}
|
||||||
|
<View className="flex-row justify-center mt-8 mb-4">
|
||||||
|
<Text className="text-gray-600">Already have an account? </Text>
|
||||||
|
<TouchableOpacity onPress={() => router.push('/login')}>
|
||||||
|
<Text className="text-[#bb0718] font-medium">Login</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
51
app/welcome.tsx
Normal file
51
app/welcome.tsx
Normal file
@ -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 (
|
||||||
|
<SafeAreaView className="flex-1 bg-white">
|
||||||
|
<StatusBar barStyle="dark-content" />
|
||||||
|
|
||||||
|
<View className="flex-1 justify-between px-6 py-10">
|
||||||
|
{/* Logo and Welcome Text */}
|
||||||
|
<View className="items-center mt-10">
|
||||||
|
<View className="w-32 h-32 items-center justify-center bg-[#ffd60a] rounded-full mb-8">
|
||||||
|
<Feather name="book-open" size={60} color="#bb0718" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Text className="text-4xl font-bold text-center">Welcome to ChefHai</Text>
|
||||||
|
<Text className="text-gray-600 text-center mt-4 text-lg">
|
||||||
|
Discover, cook and share delicious recipes with food lovers around the world
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Food Image */}
|
||||||
|
<View className="items-center my-8">
|
||||||
|
<Image
|
||||||
|
source={{ uri: "/placeholder.svg?height=300&width=300&query=colorful food dishes arrangement" }}
|
||||||
|
className="w-72 h-72 rounded-full"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Buttons */}
|
||||||
|
<View className="space-y-4 mb-6">
|
||||||
|
<TouchableOpacity
|
||||||
|
className="bg-[#ffd60a] py-4 rounded-xl mb-4"
|
||||||
|
onPress={() => router.push('/login')}
|
||||||
|
>
|
||||||
|
<Text className="text-center font-bold text-lg">Login</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
className="bg-white border border-[#ffd60a] py-4 rounded-xl"
|
||||||
|
onPress={() => router.push('/signup')}
|
||||||
|
>
|
||||||
|
<Text className="text-center font-bold text-lg text-[#bb0718]">Sign Up</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
10
package-lock.json
generated
10
package-lock.json
generated
@ -22,6 +22,7 @@
|
|||||||
"expo-image-picker": "~16.1.4",
|
"expo-image-picker": "~16.1.4",
|
||||||
"expo-linking": "~7.1.4",
|
"expo-linking": "~7.1.4",
|
||||||
"expo-router": "~5.0.6",
|
"expo-router": "~5.0.6",
|
||||||
|
"expo-secure-store": "~14.2.3",
|
||||||
"expo-splash-screen": "~0.30.8",
|
"expo-splash-screen": "~0.30.8",
|
||||||
"expo-status-bar": "~2.2.3",
|
"expo-status-bar": "~2.2.3",
|
||||||
"expo-symbols": "~0.4.4",
|
"expo-symbols": "~0.4.4",
|
||||||
@ -6439,6 +6440,15 @@
|
|||||||
"node": ">=10"
|
"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": {
|
"node_modules/expo-splash-screen": {
|
||||||
"version": "0.30.8",
|
"version": "0.30.8",
|
||||||
"resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-0.30.8.tgz",
|
"resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-0.30.8.tgz",
|
||||||
|
|||||||
@ -40,7 +40,8 @@
|
|||||||
"react-native-screens": "~4.10.0",
|
"react-native-screens": "~4.10.0",
|
||||||
"react-native-web": "~0.20.0",
|
"react-native-web": "~0.20.0",
|
||||||
"react-native-webview": "13.13.5",
|
"react-native-webview": "13.13.5",
|
||||||
"tailwindcss": "^3.4.17"
|
"tailwindcss": "^3.4.17",
|
||||||
|
"expo-secure-store": "~14.2.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.25.2",
|
"@babel/core": "^7.25.2",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user