Releases: ewuwise/FoodRecipe
Releases · ewuwise/FoodRecipe
Release list
FoodRecipe 2.0 - "Culinary Connect"
FoodRecipe Enhanced: User Authentication, Social Sharing & API Integration
I'll extend the FoodRecipe application with additional enterprise-level features. Here's the enhanced version with user authentication, social sharing, and API integration.
Enhanced File Structure
FoodRecipe/
├── package.json (updated)
├── App.js (updated)
├── src/
│ ├── navigation/
│ │ ├── index.js (updated)
│ │ └── AuthNavigator.js (new)
│ ├── redux/
│ │ ├── store.js (updated)
│ │ ├── favoritesSlice.js (updated)
│ │ ├── authSlice.js (new)
│ │ └── recipesSlice.js (new)
│ ├── screens/
│ │ ├── Auth/
│ │ │ ├── LoginScreen.js (new)
│ │ │ ├── RegisterScreen.js (new)
│ │ │ └── ProfileScreen.js (new)
│ │ ├── WelcomeScreen.js
│ │ ├── HomeScreen.js (updated)
│ │ ├── RecipeDetailScreen.js (updated)
│ │ ├── FavoriteScreen.js (updated)
│ │ ├── MyRecipeScreen.js (updated)
│ │ ├── CustomRecipesScreen.js (updated)
│ │ └── RecipesFormScreen.js (updated)
│ ├── components/
│ │ ├── categories.js (updated)
│ │ ├── recipes.js (updated)
│ │ ├── SocialShare.js (new)
│ │ └── RatingComponent.js (new)
│ ├── api/
│ │ ├── apiClient.js (new)
│ │ ├── recipesApi.js (new)
│ │ └── authApi.js (new)
│ ├── utils/
│ │ ├── validators.js (new)
│ │ ├── socialSharing.js (new)
│ │ └── constants.js (new)
│ └── config/
│ └── config.js (new)
1. Updated package.json
{
"name": "FoodRecipe",
"version": "2.0.0",
"main": "node_modules/expo/AppEntry.js",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web"
},
"dependencies": {
"@react-native-async-storage/async-storage": "1.19.3",
"@react-navigation/native": "^6.1.7",
"@react-navigation/native-stack": "^6.9.13",
"@reduxjs/toolkit": "^1.9.7",
"axios": "^1.6.2",
"expo": "~49.0.15",
"expo-status-bar": "~1.6.0",
"expo-sharing": "~11.3.0",
"expo-image-picker": "~14.3.2",
"expo-notifications": "~0.20.1",
"react": "18.2.0",
"react-native": "0.72.6",
"react-native-reanimated": "~3.3.0",
"react-native-responsive-screen": "^1.4.2",
"react-native-safe-area-context": "4.6.3",
"react-native-screens": "~3.22.0",
"react-redux": "^8.1.3",
"react-native-vector-icons": "^10.0.0",
"formik": "^2.4.5",
"yup": "^1.3.3"
},
"devDependencies": {
"@babel/core": "^7.20.0"
},
"private": true
}2. Updated App.js
import React, { useEffect } from 'react';
import { Provider } from 'react-redux';
import { store } from './src/redux/store';
import AppNavigation from './src/navigation';
import * as Notifications from 'expo-notifications';
// Configure notifications
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: true,
}),
});
export default function App() {
useEffect(() => {
// Request notification permissions
const requestPermissions = async () => {
const { status } = await Notifications.requestPermissionsAsync();
if (status !== 'granted') {
alert('No notification permissions!');
}
};
requestPermissions();
}, []);
return (
<Provider store={store}>
<AppNavigation />
</Provider>
);
}3. New src/navigation/AuthNavigator.js
import React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import LoginScreen from '../screens/Auth/LoginScreen';
import RegisterScreen from '../screens/Auth/RegisterScreen';
import ProfileScreen from '../screens/Auth/ProfileScreen';
const Stack = createNativeStackNavigator();
const AuthNavigator = () => {
return (
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="Login" component={LoginScreen} />
<Stack.Screen name="Register" component={RegisterScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>
);
};
export default AuthNavigator;4. Updated src/navigation/index.js
import * as React from "react";
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import { useSelector } from 'react-redux';
import HomeScreen from "../screens/HomeScreen";
import WelcomeScreen from "../screens/WelcomeScreen";
import MyRecipeScreen from "../screens/MyRecipeScreen";
import CustomRecipesScreen from "../screens/CustomRecipesScreen";
import RecipesFormScreen from "../screens/RecipesFormScreen";
import FavoriteScreen from "../screens/FavoriteScreen";
import RecipeDetailScreen from "../screens/RecipeDetailScreen";
import AuthNavigator from "./AuthNavigator";
const Stack = createNativeStackNavigator();
function AppNavigation() {
const isAuthenticated = useSelector((state) => state.auth.isAuthenticated);
return (
<NavigationContainer>
<Stack.Navigator
initialRouteName={isAuthenticated ? "Home" : "Welcome"}
screenOptions={{ headerShown: false }}
>
{!isAuthenticated ? (
<>
<Stack.Screen name="Welcome" component={WelcomeScreen} />
<Stack.Screen name="Auth" component={AuthNavigator} />
</>
) : (
<>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="RecipeDetail" component={RecipeDetailScreen} />
<Stack.Screen name="MyFood" component={MyRecipeScreen} />
<Stack.Screen name="CustomRecipesScreen" component={CustomRecipesScreen} />
<Stack.Screen name="RecipesFormScreen" component={RecipesFormScreen} />
<Stack.Screen name="FavoriteScreen" component={FavoriteScreen} />
<Stack.Screen name="Profile" component={AuthNavigator} />
</>
)}
</Stack.Navigator>
</NavigationContainer>
);
}
export default AppNavigation;5. New src/redux/authSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { loginUser, registerUser, logoutUser } from '../api/authApi';
// Async thunks for authentication
export const login = createAsyncThunk(
'auth/login',
async ({ email, password }, { rejectWithValue }) => {
try {
const response = await loginUser(email, password);
await AsyncStorage.setItem('userToken', response.token);
await AsyncStorage.setItem('userData', JSON.stringify(response.user));
return response;
} catch (error) {
return rejectWithValue(error.response?.data?.message || 'Login failed');
}
}
);
export const register = createAsyncThunk(
'auth/register',
async ({ name, email, password }, { rejectWithValue }) => {
try {
const response = await registerUser(name, email, password);
await AsyncStorage.setItem('userToken', response.token);
await AsyncStorage.setItem('userData', JSON.stringify(response.user));
return response;
} catch (error) {
return rejectWithValue(error.response?.data?.message || 'Registration failed');
}
}
);
export const logout = createAsyncThunk(
'auth/logout',
async (_, { rejectWithValue }) => {
try {
await logoutUser();
await AsyncStorage.removeItem('userToken');
await AsyncStorage.removeItem('userData');
return null;
} catch (error) {
return rejectWithValue('Logout failed');
}
}
);
const authSlice = createSlice({
name: 'auth',
initialState: {
user: null,
token: null,
isAuthenticated: false,
isLoading: false,
error: null,
},
reducers: {
setCredentials: (state, action) => {
state.user = action.payload.user;
state.token = action.payload.token;
state.isAuthenticated = true;
},
clearError: (state) => {
state.error = null;
},
},
extraReducers: (builder) => {
builder
// Login
.addCase(login.pending, (state) => {
state.isLoading = true;
state.error = null;
})
.addCase(login.fulfilled, (state, action) => {
state.isLoading = false;
state.isAuthenticated = true;
state.user = action.payload.user;
state.token = action.payload.token;
})
.addCase(login.rejected, (state, action) => {
state.isLoading = false;
state.error = action.payload;
})
// Register
.addCase(register.pending, (state) => {
state.isLoading = true;
state.error = null;
})
.addCase(register.fulfilled, (state, action) => {
state.isLoading = false;
state.isAuthenticated = true;
state.user = action.payload.user;
state.token = action.payload.token;
})
.addCase(register.rejected, (state, action) => {
state.isLoading = false;
state.error = action.payload;
})
// Logout
.addCase(logout.pending, (state) => {
state.isLoading = true;
})
.addCase(logout.fulfilled, (state) => {
state.isLoading = false;
state.isAuthenticated = false;
state.user = null;
state.token = null;
})
.addCase(logout.rejected, (state, action) => {
state.isLoading = false;
state.error = action.payload;
});
},
});
export const { setCredentials, clearError } = authSlice.actions;
export default authSlice.reducer;6. New src/redux/recipesSlice.js
...