Hotpotato
Hotpotato is a recipe portfolio App that assists users to discover and comment new recipes. It is a fullstack React App made with a Redux state manager and a backend using Python, Flask, SQL-Alchemy, and PostgresSQL.
-
View the Hotpotato App Live
-
It is modeled after the Behance App
-
Contains recipes for Vegetarians, Vegans, and Gluten-Free diets.
-
Reference to the Hotpotato Wiki Docs
Table of Contents |
---|
1. Features |
2. Installation |
3. Technical Implementation Details |
4. Future Features |
5. Contact |
6. Special Thanks |
Technologies
Features
Sign In and Sign Up
Feed Page
Hotpotato feed displays all recipes and chefs Discover and search for new recipes
Sort Recipes in Feed
Sort Recipes based on a category
View Recipe
Single recipe of name, photos, ingredients, directions, and comments
Add Recipe
Add a new recipe to the database Cancel adding recipe
Create, Read, Update, Delete Recipe Preparations
View preparations to make recipe Edit and Add a recipe preparation(s) in the database
Create, Read, Update, Delete Recipe Ingredients
View Ingredients to make recipe Edit and Add a recipe preparation(s) in the database
Comment
Users can add comments for a recipe
Follow
Follow or unfollow a chef
Installation
To build/run project locally, please follow these steps:
- Clone this repository
git clone https://github.com/nicopierson/hotpotato.git
- Install Pipfile dependencies and create the virtual environment
pipenv install
- Install npm dependencies for the
/react-app
cd react-app
npm install
-
In the
/
root directory, create a.env
based on the.env.example
with proper settings -
Setup your PostgreSQL user, password and database and ensure it matches your
.env
file -
In the root folder, create the database by running in the terminal:
flask db create
- In the root folder, migrate tables to the database by running in the terminal:
flask db migrate
- In the root folder, seed the database by running in the terminal:
flask seed all
- Start the flask backend in the
/
root directory
flask run
- Start the frontend in the
/react-app
directory
npm start
Technical Implementation Details
Follow
Follow feature was a key element for our project and we implemented by creating a self-referential table from the Users table. It was also necessary to add class methods to follow and to unfollow a user or chef. It was challenging to integrate the table and append or remove users to the table.
Part of our user model is shown below:
follows = db.Table(
"follows",
db.Column("user_id_follow_owner", db.Integer,
db.ForeignKey("users.id")),
db.Column("user_id_follower", db.Integer, db.ForeignKey("users.id"))
)
followers = db.relationship(
"User",
secondary=follows,
primaryjoin=(follows.c.user_id_follow_owner == id),
secondaryjoin=(follows.c.user_id_follower == id),
backref=db.backref("follows", lazy="dynamic"),
lazy="dynamic"
)
def follow(self, user):
if not self.is_following(user):
self.follows.append(user)
return user
return False
def unfollow(self, user):
if self.is_following(user):
self.follows.remove(user)
return user
return False
In order to connect the backend to the frontend, we connected the follows
api routes to update the following in the redux store. When the Follow component button is clicked, either a removeFollowing or createFollowing dispatch action is called to update the follow and profile slice of state in redux store. As a result the Profile page will re-render because React notices a change in the profile state and updates the followers attribute and the follow button.
export const removeFollowing = (id) => async (dispatch) => {
const response = await fetch(`/api/follows/users/${id}`, {
method: 'DELETE',
});
if (response.ok) {
await dispatch(deleteFollowing(id));
await dispatch(getProfile(id));
return response;
} else {
return ['An error occurred. Please try again.']
}
}
export const createFollowing = (id) => async (dispatch) => {
const response = await fetch(`/api/follows/users/${id}`, {
method: 'POST',
});
if (response.ok) {
const { following } = await response.json();
await dispatch(addFollowing(following));
await dispatch(getProfile(id));
return following;
} else {
return ['An error occurred. Please try again.']
}
}
Integrating React Carousel
In order to show more than main thumbnail, we integrated a third-party react component.
Code snippet is shown here:
<Carousel
className='recipe-carousel'
renderArrow={arrows}
>
{ getPhotos()?.map(recipe => (
<img
src={recipe.img_url}
alt={recipe}
key={recipe.id} className='recipe-carousel-images'
/>
))}
{ getVideos()?.map(video => (
<ReactPlayer url={video}></ReactPlayer>
))
}
{addVideo &&
<ReactPlayer url={videoUrl}></ReactPlayer>
}
</Carousel>
Future Features
-
Search - search recipes or chefs
-
Edit Profile - users edit profile info and add banner
-
Add Tags - add tags to recipes and profile