With Cookbook, you can save recipes from any website onto your account to view all in one place. Recipes can be scheduled for any number of dates and meal of the day. You can view all information about a recipe including scheduled time, cook time, servings, ingredients, and instructions. Recipes from allrecipes.com and geniuskitchen.com support a checklist for the list of instructions. You can also view a calendar that shows when all of your recipes are scheduled. You can reschedule recipes and copy the previous week’s recipes for easy scheduling! If you choose to support the Cookbook team by purchasing a subscription through the Settings page, you can generate a grocery list for any date range of recipes that are from allrecipes.com or geniuskitchen.com.
- Vu Cao
- Katie Gorbell
- Arthur Pisakhov
- Braden Walker
- React.js
- Apollo Client
- LESS
- GraphQL
- Prisma
-
React.js:
- Based on reusable components to build an entire app
- Allows each team member to work on a separate component at one time
- High performance with the virtual DOM
- Powerful dev tools for faster debugging
- Stable future. It’s backed by a strong company and has a large community
- Code is shared between web and mobile apps
-
Apollo Client:
- Works well with React.js due to use of components
- Tracks
loading
anderror
states automatically so we can easily handle each - Comes with an intelligent cache out of the box, so data is normalized and consistent across components
- Apollo Links make the client extensible to accomodate more advanced features.
- GraphQL Client that sends a query request to server. After server responds it will feed the data back to Apollo which will then pass it back into our frontend client(React).
-
LESS:
- Variables keep styling consistent and allow for easy app-wide changes
- Mixins allow styling to be reused and makes code DRY
- Nesting elements greatly organizes code and maximizes readability
- Values can be calculated with arithmetic operations
-
GraphQL:
- GraphQL the identity is separate from how you fetch it, whereas in REST API the endpoint you call is the identity of that object.
- Useful in our specific project because certain endpoints have different id’s based on recipe or ingredients involved.
- Can traverse from the entry point to related data while in REST you have multiple endpoints to call to fetch these related data’s. Especially prominent in our project because many recipes will have a common ingredient amongst one another. eg; salt.
-
Prisma:
- Prisma supports many of the popular known databases such as MySQL, MongoDB, PostgreSQL, etc.
- Prisma acts as the bridge between all the databases and the backend server, GraphQL. It can access multiple databases simultaneously and simplifies that use by letting the user read and write data to the database using their preferred programming language.
- Prisma analyses our database schema and generates a user-friendly data-model that can easily generate mutations and queries.
- Prisma also turns our DB into a GraphQL API.
- No boilerplate for CRUD or filters.
- Added data to datamodel.prisma
- Prisma Reset
- Deployed (except for local because of post-deploy bug)
- Prisma.graphql changed
- Change schema.graphql
- Mutation and Query Resolver
Authentication is handled through the 3rd party service Auth0. This allows users to securely sign in with a classic email and password, as well as social accounts like Facebook and Google. It is secured with OAuth 2.0 so that users can grant limited access on their social account to our Cookbook site. It also uses JSON Web Tokens to securely pass information down from our front end client to our back end server.
Authorization is handled with Javascript Web Tokens sent from the front end to our back end. Using Apollo Client, we set the token from Auth0 onto the request headers for our back end to decode on every request. Our back end verifies the token and decodes it, then places the decoded information on the server context. Every query and mutation has access to the context, so it will only return information that is available to that specific user.
We manually tested every pull request before merging it with our master branch using tools including Chrome Dev Tools, React Dev Tools, and GraphQL Playground. Every pull request was reviewed by a team member who did not publish the PR, and a project manager who would then merge the PR with the master branch. Netlify also checks for building errors on each pull request so we would know whether or not our current branch was safe to deploy. Any bugs that did result from faulty code were noted on our Trello board for the group to resolve.
Heroku Deploy: https://lambda-cookbook.herokuapp.com/ // lambda-cookbook.netlify.com
- Heroku Login
- heroku apps:create lambda-cookbook
- git remote add heroku-backend https://git.heroku.com/lambda-cookbook.git
- git subtree push --prefix Backend heroku-backend master (after every redploy)
- Heroku Settings > Config Variables
- Launch
REACT_APP_STRIPE_TEST_KEY="" REACT_APP_CURR_ENV="dev" REACT_APP_BACKEND_URL=""
FRONTEND_URL="http://localhost:3000" PRISMA_ENDPOINT="" PRISMA_SECRET="" APP_SECRET="" STRIPE_SECRET=""
auth0Sub
and email
come from the id_token provided by Auth0.
firstName
and lastName
are optional inputs found on the Settings page of the app.
type User {
id: ID! @unique
auth0Sub: String! @unique
firstName: String
lastName: String
email: String! @unique
isSubscribed: Boolean! @default(value: false)
recipes: [Recipe!]! @relation(name: "UserRecipe", onDelete: CASCADE)
subscriptions: [Subscription!]!
@relation(name: "UserSubscription", onDelete: CASCADE)
}
prepTime
is the total preparation and cook time of a recipe.
type Recipe {
id: ID! @unique
title: String!
prepTime: String
servings: String
image: String!
url: String!
createdBy: User! @relation(name: "UserRecipe")
ingredients: [Ingredient!]!
@relation(name: "RecipeIngredient", onDelete: CASCADE)
instructions: [Instruction!]!
@relation(name: "RecipeInstruction", onDelete: CASCADE)
events: [Event!]! @relation(name: "RecipeEvent", onDelete: CASCADE)
}
quantity
is a string that can take fractions.
type Ingredient {
id: ID! @unique
name: String!
quantity: String!
recipe: Recipe! @relation(name: "RecipeIngredient")
}
type Instruction {
id: ID! @unique
stepNum: Int!
description: String!
isCompleted: Boolean! @default(value: false)
recipe: Recipe! @relation(name: "RecipeInstruction")
}
mealType
is one of 5 values; breakfast, lunch, dinner, dessert, snack
type Event {
id: ID! @unique
recipe: Recipe! @relation(name: "RecipeEvent")
date: String!
mealType: String!
}
type Subscription {
id: ID! @unique
amount: Int!
currency: String!
user: User! @relation(name: "UserSubscription")
charge: String!
createdAt: DateTime!
updatedAt: DateTime!
}
Returns the user from our database that matches the user on context
currentUser(auth0Sub: String): User
Returns the recipe with the provided recipe ID
recipe(where: RecipeWhereUniqueInput!): Recipe!
Returns all recipes that belong to the current user on context
recipes(where: RecipeWhereInput): [Recipe]!
instructions(where: InstructionWhereInput): [Instruction]!
ingredients(where: IngredientWhereInput): [Ingredient]!
Returns all events that were created by the current user on context.
events(where: EventWhereInput): [Event]!
Gets the user info from context and stores it in the database
signup: User!
Updates the provided user with the provided user data
updateUser(data: UserUpdateInput!, where: UserWhereUniqueInput!): User
Creates a subscription using the provided Stripe token for the user on context
createSubscription(token: String!): Subscription!
Creates a recipe with the provided info for the user on context.
createRecipe(
title: String!
prepTime: String!
servings: String!
image: String!
url: String!
mealType: String
dates: [String]!
): Recipe!
Deletes the specified recipe
deleteRecipe(where: RecipeWhereUniqueInput!): Recipe
Creates an event with the provided date
and mealType
for the provided recipe ID
createEvent(date: String!, mealType: String!, recipe: String!): Event
Deletes the event with the provided event ID
deleteEvent(where: EventWhereUniqueInput!): Event
Creates an instruction with the provided stepNum
and description
for the provided recipe ID
createInstruction(
stepNum: Int!
description: String!
recipe: String!
): Instruction
Creates an ingredient with the provided name
and quantity
for the provided recipe ID
createIngredient(
name: String!
quantity: String!
recipe: String!
): Ingredient
We use Stripe API for our subscription. On front-end we use stripe-checkout
to send with user information like credit card, name, amount charged, and send it to Stripe server. The server then sends back a token, we use that token in the back-end to create a Subscription for user, and switch user status to Subscribed.
Free users have full access to create recipe and schedule meals in recipes page and calendar page. Only premium users have access to Grocery List, where they can generate a list of ingredients for shopping purpose.
The overall style is very round. Components have rounded corners. Buttons are pill shaped or completely circular (depending on the width of the content inside). Both fonts are sans-serif and circular as well. There should be plenty of margin and padding around all elements. Elements that are functionally distinct should have more margin between them than elements that interact with each other. (i.e. the meal type buttons on the “Create” page are closer to the calendar than the search bar or recipe preview)
Each page has a unique color scheme based on the navigation bar at the top of the page. One color takes over the page, while the other color should accent functional components. For example, most of the “Create” page is red, but buttons and inputs will turn orange when hovered over. Some colors will carry over into other pages. Recipes on the “Calendar” page are dark blue for readability and consistency.
The top image on our landing page was retrieved from the free stock photo website: pexels. The exact URL for our image can be found here: https://www.pexels.com/photo/basil-delicious-food-ingredients-459469/
All icons on our website were created locally, or imported from the the FontAwesome library.
Lastly, the illustrations on our landing page were taken from unDraw at: https://undraw.co/illustrations
- Aqua Haze
#EBF4F4
- Heather
#BCC9D2
- Pickled Bluewood
#343E5A
- Roman
#DE6A5A
- Chardonnay
#FED092
- White Linen
#F5E9DF
- Questrial (regular 400)
- Muli (extra-light 200, bold 700)