The monorepo for the Helpshift web dev workshop
Note This step is to be performed only once
- Step 1 - Click on code
- Step 2 - Click on create codespace on main and wait for setup
Note You would need to perform this setup again whenever you restart your codespace
- Step 1 - Setup and start frontend by executing
make serve-frontend
on the terminal
- Step 2 - Go to ports and choose port 4400
Screen.Recording.2023-04-05.at.8.58.10.PM.mov
- Step 3 - Change port visibility to public for port 4400 (right click on the visibility column to see options)
Screen.Recording.2023-04-05.at.8.59.35.PM.mov
- Step 4 - Copy the frontend local address and paste in a new tab in browser
Screen.Recording.2023-04-05.at.9.08.54.PM.mov
Note You would need to perform this setup again whenever you restart your codespace
- Step 1 - Setup and start the backend server by executing
make serve-backend
in a new terminal
Screen.Recording.2023-04-05.at.9.22.05.PM.mov
- Step 2 - Go to ports and choose port 5000
Screen.Recording.2023-04-05.at.9.23.30.PM.mov
- Step 3 - Change port visibility to public for port 5000 (right click on the visibility column to see options)
Screen.Recording.2023-04-05.at.9.24.52.PM.mov
Note Ensure that your backend server is running.
- Step 1 - open a new terminal in your codespace
- Step 2 - run
node backend/scripts/seed.js
Step 1 - Copy the frontend local address and paste in a new tab in browser
Screen.Recording.2023-04-05.at.9.08.54.PM.mov
Step 2 - Click on registration and create a new user
Screen.Recording.2023-04-05.at.9.46.40.PM.mov
And voila, you're setup is complete !!!
You can start by running
./start
The project has different levels (chapters) each focusing on a particular area. You can switch to different levels by,
./level_up
You can start over again by running
./restart
The frontend is created using Next.js. You can think of Next.js like React.js with some bells and whistles. With Next.js we can easily define routes, fetch data from the server using special functions, and make our deployments easier using "Vercel".
You might feel a bit scared looking at all these folders and files, but it'll make sense in a few minutes.
The only folders you should be concerned with are:
src
stands for "source". This is where all the source code existssrc/pages
- These are all the routes that your users will be able to navigate tosrc/components
- This is where we store reusable or logically separate chunks of our code so we can import and use them in ourpages
For this checkpoint, we'll be looking at the following file: pages/auth/register.jsx
.
We have a form that accepts a display name, and an email. However, we still need to add a "password" input and a "handle", with which the end-user can be easily identified. Take a look at the "name" and "email" fields, and try to implement "password" and "handle" inputs. If you're feeling stuck, don't be afraid to use use online resources (cough, Chat, cough, GPT). But we highly recommend trying it out by yourself first to build an understanding of how forms are created.
Some helpful notes for you:
onChange
attribute in the<input />
tag informs you whenever the input value changes. Use that to set your variable.- The password text should not be visible! Try searching for "HTML input types"
Key learning: How data flows from a user's keyboard into the actual application
The registration button looks a bit weird, don't you think? Let's use Tailwind classes to spruce it up! Tailwind is insanely easy to understand because each class (usually) applies just one style.
Check out some examples of Tailwind buttons online and try some stuff out yourself: https://flowbite.com/docs/components/buttons/
We also want to make the button look faded when it is disabled. How can we do that?
Key learning: How atomic CSS can be used to compose complex UIs in a very simple way
Our product needs some branding. Can you figure out a nice way of adding a "Dog" icon to the top of our form?
Key learning: How external resources can be added to a React application.
APIs are the backbone of web development. Whenever we want to do anything persistent (long-lasting), such as storing a user's information in a database, we have to make an API call to our application server.
When users click on the "Register" button, we should be able to detect it.
Can you hook up the <form>
tag to fire the onFormSubmit
function?
We will use the "fetch" function to submit our form to the backend. Although we haven't yet implemented the registration endpoint, we will be sending name
, email
, handle
, and password
in the body of our POST request.
Check out an example of fetch
here: https://developer.mozilla.org/en-US/docs/Web/API/fetch#examples
Key learning: How data flows from the client (browser) to the server
Our backend is written using Node.js. Again, the folder structure might seem a bit daunting at first, but here's what you need to know:
- All the entities in our system, and their relationships are defined in the
models
folder - All the "endpoints" with which we can interact with the database are present in the
routes
folder - Whenever a request hits a "route", a controller (a function that can process requests) will be called. Controllers can interact with the database and return a response, which will be made available to the frontend. Controllers are present in a folder called
controllers
We will learn more about database modelling in a future checkpoint. For now, this is created for you, and you have to focus on adding a route and a controller for this route.
We are firing a network request on POST /user
from the frontend, so we should define the relevant route on the backend in routes/users.js
.
Key learning:
API calls on the frontend are mapped to routes
on the backend
We are using an ORM (a library that helps us interact easily with the underlying database) called Sequelize to store and retrieve data from the database. Can you write a query to create a new user using the request data?
Note how we have hashed the user's password since it's not a good practice to store plain-text passwords.
Key learning: How data is stored in a database so it can last there long-term!
Once we store the data successfully, we want to return a response. If the database entry was successful, return a successful response, else we return an error. Look around in the codebase to see how we can do this!
Key learning: Each route can either be successful or unsuccessful. We must account for both cases when creating a controller.
For this checkpoint, we'll be looking at the following file: /src/components/Tweet/ComposeTweet.jsx
.́
After going through the above 3 checkpoints, now you have a basic understanding of how to create a form, apply styling to it, and how fire API, write a query to get and store the data inside the database
Now we going to learn how users would write a tweet on the UI?, excited 😄?
We want users to write a tweet and submit the tweet on the UI. so how could we achieve that?
- What do you think what kinds of HTML input elements should we use here for both?
You have might have noticed this on Twitter when you write a tweet your text area content increases as per your content length. Now, according to what we had plan to implement will not work here, right?
-
So can you find the react library which will auto-resize our textarea?
-
We want the user to submit their tweet so can you construct a button for the same?
-
We also want to indicate the character count while the user writes their tweet along with the character limit we have set.
Should the tweet submit button be visible when there is no tweet content present? Is that good user experience (UX)?
- Could you think? How could we disable the button when tweet content is empty?
Twitter has this constraint where it restricts the number of characters in a tweet.
- So can you think of how could we restrict the end-user when it reaches the limit
It would be good UX if you indicate something on the UI so that they know that tweet content length is about to reach the limit
- So can you make the character limit indicator red when a user reaches the limit? It is the good cherry on top of the cake functionality
We have constructed the basic UI to compose the tweet. Now we want to send the tweet content to our server, which will then stored it inside a database
So what is the first step to achieving the above result? Fire a request to some endpoint right?
- Which HTTP request method do you think is most appropriate to use here?
What should be the behavior if the request succeeded and what if get failed? any thoughts?
How are we going to store the tweet content in the database, we can be going to learn in the next checkpoint
Frontend will trigger a POST request, so we should define the relevant route which is parsed through the middlewares on the backend in routes/tweet.js.
We have added a route, now it's time to make changes in controller tweet.js. Controller accepts the request and stores data and generates a response for Frontend.
Using errorHandler middleware for formatting all responses.
Response middlewares generally helps to maintain the shape of response, it can make sure that the JSON is in same format each time. Fix the code in createUser endpoint and use middleware.
We are building the database model for a woof but we don't know how should we be storing the woof body. Help us complete the data model.
To fetch the data for showing the home feed, we are making a couple of calls to the database. But we are not sure how to make all the database queries. Help us complete the whole process.
All of our beta testing customers have said they would prefer see the latest woofs as the most recent one on their feed. Let's make sure the customers are happy :)
Frontend and backend have decided a contract for every API call. But it is not necessary that the data being stored in the database is in the same format. We need to make sure the data being returned to the frontend follows the contract decided.
We need to make a request to the backend to get tweets. We have to make sure that we send cookies to the backend in our request. Without our cookie, the backend will not know who is requesting for this information and can reject our request.
After Task 1, we can insert the tweet data into our state. This will allow for tweets to show up.
Just like we can pass arguments to functions, we can pass props to components. Pass the tweets that we set in the state into this component as a prop.
The URL that we call is going to be at the route /followers/:handle
. The user's handle is available in the params
object that getServerSideProps
is called with. And finally, the base URL is available as a constant in BASE_URL
. Can you call the correct URL?
We can pass this data as props to the component. Can you assign the followers data to the users
key? It is available in data.followers
.
It's possible that the backend doesn't give us any followers data and crashes (for some reason). How would we handle this? Remember, we have handled the case where zero users are present.
Can you use the UserList
component to render the followers data now? The followers are available to us in the users
prop.
Add appropriate routes for both actions, liking and un-liking a tweet.
Add controllers for the handling actions 'like' and 'un-like'. The correct tables in the database must be updated, for each action. Associate the controllers for liking and un-liking a tweet with the appropriate routes.
Return the correct response status codes for the controllers, and add error handling.
We will be using the like count and state of like, and use that for rendering and handling the UI
- Use the appropriate value & setter from the store for like count
- Use the appropriate value & setter from the store for like state
- Set the weight for Heart component based on value of like
- Customize the classes for Heart component based on value of like
- Show the number of likes text based on value of like
- Handle onClick based on whether the tweet is already liked or not
- Use the correct HTTP method for the
/tweet/like/
route - Use the correct Content-Type of the
tweet/like
route - Update the like count and state of like onSuccess
Task 4: Find the correct position for handling button state. This is common scenario in FE where based on the state of the request, we update the UI
- For unlike tweet, enable/disable the button correctly before firing the request
- For unlike tweet, enable/disable the button correct after the request completes
- Set the disabled prop correctly based on already liked or not