Skip to content

Releases: ewuwise/FoodRecipe

FoodRecipe 2.0 - "Culinary Connect"

Choose a tag to compare

@ewuwise ewuwise released this 04 Dec 14:31
263ba7e

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

...
Read more