Hi, welcome to the last post of Online Food Ordering App series.
In the previous posts we built API endpoints for authentication and managing orders. We've also built our frontend react and react-native apps and connected them to the API endpoints.
In this post, we are going to implement the view orders list, view single order, update order status, view menu, and place order features on our front-end apps.
Before we start, take a look at this PR and update the backend code. We added the payments endpoint, a script to create the menu and we updated the fetch orders list to accommodate both the admin and the customer.
Before we begin our implementation, let's think for a minute about the user flow we want for our customer.
A logged-in customer launches the app and they immediately see a list of menu items divided into 3 tabs (Breakfast, Lunch/Dinner, and Drinks). Each item has an image, a name, a short description, a cost/price, and size. To switch to a different tab, a user swipes the screen left or right, or they tap on the tab name. To add an item to the cart, a user simply taps on the item. Tapping on the item already in the cart increases its quantity by 1. To remove an item from the cart a user simply taps on the item from the cart screen. From the cart screen, a user can navigate to the payment screen where they will be able to confirm their order by making payment by card.
A user can also see the list of orders he/she has placed and their details by tapping on the basket icon in the bottom navigator. Finally, a user will be able to see his/her account information by tapping on the account icon in the bottom navigator.
The screens of our app will be divided into 2 main parts (
AuthStack will contain all the screens related to authentication (
HomeStack will contain nested stacks (
MainStack will contain screens allowing the user to view the menu, interact with the cart, and make a payment.
OrdersStack as the name suggest will contain screens for viewing the list of orders a user has placed and each order details.
AccountStack will contain just one screen to display the user's account information and a logout button.
Great! Let's get started.
- Install the dependencies we are going to need:
yarn add react-native-dotenv react-native-credit-card-input react-native-paper-tabs react-native-pager-view @react-navigation/material-bottom-tabs react-native-stripe-payments
- In the context directory, create a
dataReducer.jsfile and paste the following code inside:
ADD_MENU: will receive an array of menu categories and their items and will save it in our state in a variable called menu.
GET_MENU: will receive a category name then loops through the menu categories to find if that category exists then will save it's items in a variable called menuItems.
ADD_TO_CART: will receive a menu item and append it to a variable called cart.
UPDATE_CART: will receive an item then checks if that item is in the cart before replacing it with the new item.
REMOVE_FROM_CART: will receive an item id then loops through the cart array to find an item with that id and delete it.
CLEAR_CART: will remove all items in the cart array.
ADD_ORDERS: will receive an array of orders list the save it in the state in a variable called ordersList.
In this file we create a
MenuTabs component that receive 2 props:
menuItems (an array of menu items for the selected category) and
handleChangeIndex (a function to switch tabs). We created a
handleAddTocart function which will help us to modify an item before adding it to the cart and to dispatch messages after the item is added to the cart.
The component returns 3 tab screens where each tab screen will use the ListItems component to display the data or the
CustomCaption component to display that items were not found. Also, each tab screen is associated with an index number starting from 0. We'll see how this index number will be useful in a minute.
Let us now create the main screen and use the menu tabs we just created above.
- Create a
src/screens/MainScreen/MainScreen.jsfile like this:
In this file we created a
MainScreen component that fetches user data, cart and menu items from our global state. We created a
handleChangeIndex function that receives an index number (tab screen index) and dispatches a function that will trigger the
GET_MENU action. We used the useEffect hook to trigger the handleChangeIndex function when this component mounts to get data for the first tab screen.
This component will render a welcome message, the user's address, the menuTabs component and the
CartButton component to view the cart contents if the cart is not empty.
Let us now create the last screen for
- Create a
src/screens/PaymentScreen/PaymentScreen.jsfile like this:
In this file we created a
PaymentScreen component that has 2 functions:
handleCreditCardForm. When this component mounts, it displays a title, an image of credit/debit cards accepted, and a button to make payment. When the button is clicked, it triggers the
handlePaymentInit function which triggers an internal state update of
showCardForm to display the
CreditCardForm component receives an
onChange props which is a function that gets executed as we fill the form and returns a
formData object composed of 3 properties:
status. We are interested in
valid is a boolean which will be true once all the fields of the form are filled correctly.
values is an object of the form field values. It has the following properties:
number (card number),
expiry (MM/YY), and
cvc (3-digit cvc/ccv). Learn more here.
So, in the
handleCreditCardForm function we check if the user has filled the form correctly, then we extract the form values and build a
cardDetails object. We then proceed to validate the cardDetails object by using the
isCardValid method from react-native-stripe-payments.
If the cardDetails are valid, we hit our API endpoint for payments to create a
paymentIntent. The payment intent returned from calling our API contains a
clientSecret string that we use along with the cardDetails object to confirm the payment with stripe.
If the response returned from confirming the payment contains an id, that means the payment is successful then we proceed to prepare the payload for the order and hit our backend endpoint to place an order. If the order is placed successfully, we reset our stack navigation then navigate to ordersListScreen.
NOTE: this implementation is a bit naive because there are some edge cases we did not account for, for example, what if the payment is successful but the order cannot be placed? What happens then?
One solution would be to extend our order statuses and allow a user to place an order before making the payment then once the payment is confirmed we approve the order.
Lastly, we wrapped everything in a try and catch so if anything goes wrong the user will be notified via the
Make sure you update the authentication feature to use the updated services as well.
The screens for our MainStack are now done. Let us implement
- Create a
src/screens/OrdersListScreen/OrdersListScreen.jsfile like this:
OrdersListScreen component we used the useEffect hook to add a
focus event listener which will be triggered every time this screen is focused. In the event listener we fetch the list of orders and dispatch an action to save the data in
ordersList global state variable. The component will display the list of orders if found or a no orders found text. We have also implemented a
handleOrder function which will receive an id then navigates to
- Create a
src/screens/OrderDetailsScreen/OrderDetailsScreen.jsfile like this:
In this component we fetch an order's details by using the orderId parameter from props, save the data in internal state variable orderDetails then we render the information.
The screens for
OrdersStack are now done. Let us create the one screen for
- Create a
src/AccountScreen/AccountScreen.jsfile like this:
In this component we are just displaying the user information from the global state variable
auth. We have also moved our logout implementation in this component.
Now that our screens are done, let us create the stacks mentioned above.
src/navigationcreate a new directory called
stacksand inside stacks create the following files:
The last piece of the puzzle is to put together the stacks we created above and add the data context provider in
Let's do it.
src/navigation/stacks/and update it to look like this:
Let's go over what we did in this file.
HomeStack is the component that will be mounted as soon as a user logs in or when a logged-in user launches the app and he/she is authenticated. There are a couple of things we want to do before this component renders:
- We need to fetch the menu and save it in our global state.
- While fetching the menu, if the user's token happens to be expired (from the backend) we automatically log out the user (on the frontend).
- If the user's token is valid and the menu data is found or not, we proceed to render the tab navigator.
- Run the app on an emulator or a physical device and you should see the screens below:
To create a splash/launch screen, check out this article.
For reference, here's the repo for the project.
Admin view orders list, view single order and update order
For the admin app we are going to use Material UI's Collapsible Table component to display the orders. Each row in the table will have a button to reveal the details where the admin will be able to see the contents of an order along with a
Update status and
user info buttons to update the status of the order and view the details of the user respectively.
We have also implemented pagination to 5 rows per page but you can change this value according to your needs.
Great. Let's start by installing React Spring to add small animations to our app.
- Install React Spring:
$ yarn add react-spring
In this component we fetch the list of orders from the backend then use the CustomTable component to display the data.
we have also used the
useSpring hook from React Spring to add a fade animation to our component.
And that's it, the admin will now be able to view and update orders.
NOTE: There are lots of features we can add to improve our app. For instance, the first page of the dashboard could contain a summary or an overview of the data in our app like the number of orders for a given period of time, the amount of profit made, the most bought items, etc. We could also leverage the power of websockets to make our app show real-time data or add notifications for when an order is placed or any other action.
This concludes our series.
To recap, we've built a REST API using Node, Express, and Postgres then we built frontend apps in React and React-Native to use the API. We've also covered JWT authentication, unit, integration, and end-to-end testing along with continuous integration and continuous delivery (CI/CD).
I hope this series was useful to you. If you have a question, a comment, or a suggestion let me know in the comment box below.
Thank you for your time, until next time, cheers!