CS396: Winter 2022

Intro to Web Development

CS396: Winter 2022

Assignments > HW6. PhotoApp: Various Extensions

Due on Fri, 03/18 @ 11:59PM. 40 Points.

For your last homework assignment, you have two options:

  1. Implementing a React version of your Photo App
  2. Implementing a chat interface to interact with users with whom you have a mutual following relationship (you follow them AND they follow you).

Option 1: React Re-Implementation

Overview

At a high level, we want you to re-implement your HW4 code in way that follows React conventions. A few ground rules:

  1. You can use functional or class components (What’s the difference?). Sarah used class components in her demos, but go with what you prefer. We have no preference.
  2. You can use third-party libraries if you want (but it’s not required).
  3. We won’t be grading you on CSS, but please do make it look nice. You’re also welcome to customize the look and feel to make it your own.
  4. We’ve implemented a react version of the PhotoApp here: https://photo-app-secured.herokuapp.com/. Yours should function similarly.

Tips

  • If you’re running into any errors with fetch requests, you may have a few minor bugs in your REST API Endpoint. To verify (Is the bug in my React code or in my API?), try running your code using the course API by updating your React app’s proxy url address in package.json to: https://photo-app-secured.herokuapp.com/.
  • If you’re switching between the course API and your API to debug, note that you’ll either have to clear out your access_token_cookie manually (by physically deleting it using the Application panel of your browswer’s developer tools) or else use the same JWT_SECRET as the course application (set in your .env file), which is MY_SECRET (e.g., JWT_SECRET=MY_SECRET).
  • By default, your JWT token will time out every 15 minutes. To make your life easier, consider extending the life of your JWT token by adding the code below to your app.py file:
# Import timedelta at the top:
from datetime import timedelta

# Put this setting with all of your other JWT settings:
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(days=30)

Tasks

In the section below, we’ve mapped out a suggested implementation strategy. If you implement your React functionality differently, that’s fine, but we will be verifying that you did indeed make a series of components (versus just copying your HW4 file into a React folder and only making minor changes).

An example of the implemented react app can be found here.

Points Component / Task Description
2pts NavBar.js
Displays the Header
Create a NavBar component that displays the username of the logged in user, a logout button, and a link to the API tester as shown in the demo. Notes:
  • This task requires that you fetch data from the /api/profile endpoint.
  • It's OK that the /logout and /api links don't work right now.
2pts Profile.js
Display Profile
Create a Profile component that displays the current user's profile (inside of the right panel) using data from the /api/profile endpoint.
  • Hint: Since both Profile and NavBar require you to fetch data from /api/profile, you may want to put your fetch functionality in App, and then pass the requisite user information to the child components as properties.
2pts Stories.js
Display Stories
Create a Stories component that displays stories from the user's network. This component will both fetch the stories from /api/stories, and draw the stories.
2pts Suggestions.js
Display All Suggestions
Create a Suggestions component that displays suggested user accounts. This component will both fetch the suggestions from /api/suggestions, and draw the suggested users with the help of the Suggestion.js child component (see below).
5pts Suggestion.js
Display Individual Suggestion
Create a Suggestion component that will:
  • Render a representation fo each user
  • Handle the follow/unfollow fetch requests to the /api/following and /api/following/<id> endpoints.
  • Redraw the HTML after a follow / unfollow requests is successfully issued.
2pts Posts.js
Display All Posts
Create a Posts component that displays all of the posts user accounts. This component will both fetch the posts from /api/posts, and draw each post users with the help of the Post.js child component (see below).
3pts Post.js
Display Individual Post
Create a Post component so that it looks like the post from HW4. To do this:
  • The Like/Unlike functionality should be handled by a LikeButton child component (details below).
  • The Bookmark/Unbookmark functionality should be handled by a BookmarkButton child component (details below).
  • The "add comment" functionality should be handled by a AddComment child component (details below).
  • You can handle the display of the "comment button" and "last comment" any way you like. You could create a Comments and/or Comment child component, or you could render comments directly in your Post's render method.
2pts Post.js
Fetch and Redraw Post
Within your Post component, you will also have to write some code to redraw a post after its structure is modified (liked/unliked, bookmarked/unbookmarked, etc.). We recommend that you create a function called requeryPost that:
  • Re-queries the post from /api/posts/<id>
  • Updates the state of your component to reflect that the post data has changed (which will in turn re-render the component).
You will also want to give some of your child components access to this function (e.g., LikeButton, BookmarkButton, etc.) by passing a reference to this function as a property. This way, the child components can also trigger a post redraw by invoking its parent's requeryPost function.
5pts LikeButton.js
Like / Unlike Post
Create a LikeButton component that will:
  • Render a solid / filled in heart (depending on whether the post is liked / unliked by the current user).
  • Handle the like / unlike fetch requests to the /api/posts/<post_id>/likes and /api/posts/<post_id>/likes/<id> endpoints.
  • Redraw the Post if the like / unlike requests is successful (hint: use the Post's requeryPost function).
5pts BookmarkButton.js
Bookmark / Un-Bookmark Post
Create a BookmarkButton component that will:
  • Render a solid / filled in bookmark (depending on whether the post is bookmarked / un-bookmarked by the current user).
  • Handle the bookmark / un-bookmark fetch requests to the /api/posts/bookmarks and /api/bookmarks/<id> endpoints.
  • Redraw the Post if the bookmark / un-bookmark request is successful (hint: use the Post's requeryPost function).
5pts AddComment.js
Add a Comment
Create an AddComment component that will:
  • Render an "Add comment" textbox and button.
  • Handle the add comment fetch request to the /api/comments endpoint.
  • Redraw the Post (parent component) after if the "add comment" request is successful (hint: use the Post's requeryPost function).
1pt Keyboard Navigation
  • Ensure that all of the buttons are tabbable
  • Ensure that all the event handlers can be triggered using the "spacebar" or "enter / return" keys.
2pts Aria attributes Use the "aria-label" and "aria-checked" attributes (in conjunction with the role="switch" attribute) to indicate to the screen reader whether the following buttons are turned on or off:
  • Like / Unlike button
  • Bookmark / Unbookmark button
  • Follow / Unfollow button
2pts Form Accessibility
Extra handling for "Add Comment"
  • After a comment is submitted by the user, ensure that the focus is set back to the input. Hint: use a "ref".
  • Add an event handler to the input control so that it submits when the user presses the "Enter/Return" key. Here is a hint.

Extra Credit

You may earn up to 5 points extra credit. If you are working in partners, your extra credit will be split between the two of you (i.e. you will have to implement 10 points worth of extra credit to earn 5 points each).

Points Component / Task Description
5pts Deploy
Deploy your React App to Heroku
See deployment instructions below
5pts Modal Implement the "show modal" functionality using a react component. This will involve making a Modal component (and not just copying your old modal code into your react app).

Deployment Instructions (Optional)

In order to deploy your React code, you have two options.

1. Create a Stand-Alone React App

  1. Create a git repository that has your react app as a top-level directory (see suggested file structure below).
  2. Create a .gitignore file that excludes the node_modules and build directories (see below).
  3. Update all of your API urls (the ones that issue fetch requests) to point to your production REST API (or the class API if your HW5 deployment is broken).
  4. Modify your package.json file (which should be at the root of your git repo) and delete the “proxy” entry ("proxy": "http://127.0.0.1:5000",). Note that you can’t comment out entries in a JSON file, so you’ll have to actually delete it.
  5. Commit and push your new repo.
  6. Create a new Heroku instance and connect it to your new GitHub repo.
    • Deploy (just like you did in previous homeworks).
    • Note that you don’t need to configure any environment variables – Herou will automatically read your package.json file, install the dependencies, and create a production build for you.
  7. That should be it!

Suggested File Structure:

your-react-app
├── .git
├── .gitignore
├── build               # build should be excluded from the repo
├── node_modules        # node_modules should be excluded from the repo
├── package-lock.json
├── package.json
├── public
└── src

.gitignore file

node_modules
build

2. Integrate with Flask

  1. Paste your react client folder (I called mine react-client) at the root of your Flask app (see suggested dile structure below). Just for simplicity, rename that folder to react-client.
  2. Create a package.json file at the root of your Flask app (note that this is in addition to the package.json file inside of your react-client directory) that has the JSON format shown below.
    • This file tells Heroku to compile your react app from within the react-client subdirectory.
  3. Update your requirements.txt to include the following: flask-multistatic==1.0
    • You will also need to install this library on your local machine: pip3 install flask-multistatic.
  4. Modify your app.py as shown below.
  5. Ensure that your react-client/node_modules and react-client/build folders are excluded from git via your .gitignore file.
  6. Commit and push your changes to GitHub.
  7. Create a new Heroku instance and connect it to your new GitHub repo.
    • Install the Node buildpack (Settings Tab > “Add Buildpack” button > nodejs)
    • Deploy (just like you did in previous homeworks).
    • Herou will automatically read your package.json file, install the dependencies, and create a production React build for you.
  8. That should be it!

Suggested File Structure:

photo-app
├── .env
├── .git
├── .gitignore
├── Procfile
├── README.md
├── app.py
├── decorators.py
├── models
├── package-lock.json
├── package.json
├── populate.py
├── react-client
├── requirements.txt
├── sql
├── static
├── templates
├── tests
└── views

package.json file at the root of your Flask app (not the one in your react-client folder):

{
    "name": "photo-app-heroku-react-build-file",
    "version": "1.0.0",
    "description": "",
    "scripts": {
      "build": "cd react-client && npm install && npm run build"
    },
    "dependencies": {
        "cross-env": "^7.0.3"
    }
}  

app.py updates:

from flask_multistatic import MultiStaticFlask as Flask   # at the top
from flask import send_from_directory                     # at the top


# place the following after: app = Flask(__name__)
app.static_folder = [
    os.path.join(app.root_path, 'react-client', 'build', 'static'),
    os.path.join(app.root_path, 'static')
]

# modify the root path to point to your React App:
@app.route('/')
@decorators.jwt_or_login
def home():
    # https://medium.com/swlh/how-to-deploy-a-react-python-flask-project-on-heroku-edb99309311
    return send_from_directory(app.root_path + '/react-client/build', 'index.html')

Option 2: Chat Interface

Introduction

In order to build a chat interface, you will:

  1. Extend the flask code (both the client and the server functionality) from HW5. Recall: the Flask server’s job is to serve data (via the REST API) and the HTML/CSS/JavaScript files that control the user interface.
  2. Extend the web socket server you created for Lab 8. The web socket server’s job is to ensure that authenticated users can relay messages to their contacts.

Both servers will need to be run simultaneously (Flask on port 5000, Web Sockets Server on port 8081) in order to complete the assignment.

Set Up

  1. In your photo-app codebase, please create a new branch for your HW6 updates. See previous homeworks for instructions for creating a new branch.
  2. Download the Lab 8 solutions from Canvas and save them somewhere that makes sense. You will be modifying and running the server/app.py file, which will be the basis for your chat server.

Please complete the following tasks:

Points Location Task Description
5pts Flask Server REST API Endpoint
/api/contacts
Create a new REST API Endpoint, /api/contacts, that returns a list of the user's contacts. A contact is a reciprocal relationship. Given two users A and B: A follows B and B follows A.
  • The endpoint will return a list of User objects (just like the suggestions endpoint).
5pts Chat Server Authentication
JWT Validation
Instead of letting anyone use your chat server, you will now only allow authenticated users to use the chat server. Users will authenticate by including a valid JWT token (issued by your Flask app) in their chat messages. To enable this:
  • Modify the rules of the chat server so that all JSON messages require an access_token.
  • Use the jwt library (pip3 install PyJWT) to decrypt and validate the token. This includes checking that it can be decoded and that it hasn't expired.
  • To decode the token, you'll have to use the same secret as is used in your flask app.
5pts Chat Server Fetching Contacts
Track the user's network
If the message "type" is "login":
  • Store the decoded user_id (from the JWT) and the websocket in memory (so we can track which users are logged into the system).
  • Issue a request to /api/contacts (make sure you include the access_token in the header), and then store the user's contacts in the chat server's memory.
  • Broadcast a message to any user in the system who is "logged in" and a contact of the person who just logged in. This will allow the UI to display to other users of the system which of their contacts are logged in.
5pts Chat Server Relaying Messages
Make sure that messages only go to the intended recipient
  • Modify the rules of messages ("type": "message") so that a "recipient_id" is required (in addition to the access token).
  • If the recipient is logged into the chat server and in the current user's contact list, relay the message to the recipient.
5pts JavaScript Client User Interface
Chat Server Login
  • Replace the list of suggested users (in the right-hand panel) with a login button that says something like "Log into chat."
  • If one of the current user's contacts logs in (i.e. by receiving message of type "login", visually indicate that the contact has logged in in the right-hand panel
10pts JavaScript Client User Interface
Chat Box
  • If the user clicks on one of their logged in contacts, display a chat interface that can send and receive messages.
  • If the user sends a message, send it to the web socket server (along with the user_id of the intended recipient and the access_token).
  • If the user receives a message, display who it's from and the message.
5pts Accessibility User Interface
Chat Box and Contacts
Chat interfaces dynamically inject new elements into the DOM as messages are broadcasted from the web sockets server. To make a screen reader aware of these changes, please complete the following tasks:
  • Ensure that the container that holds your chat messages and contact list has the aria-live attribute (set to polite or assertive). This will indicate to your screen reader that the elements therein will be dynamic.
  • Please ensure that the user is notified by the screen reader when:
    • A new message is received
    • A new contact logs into chat
    • A new contact logs out of chat
  • To test, please see this post

Extra Credit

You may earn up to 5 points extra credit. If you are working in partners, your extra credit will be split between the two of you (i.e. you will have to implement 10 points worth of extra credit to earn 5 points each).

Points Component / Task Description
5pts Chat Interface Implement the chat interface using React instead of vanilla JavaScript.
3pts Chat Interface Modify the list of contacts in the right-hand panel such that the contacts who are logged in are displayed before the contacts who are not logged in.
5pts General Enhance your code so that you can include more than one of your contacts in the chat.

Accessibility Reflection

If you already completed this in Lab 9, you can skip it. But in case you missed it…

Accessibility Questionnaire

This quarter, we assigned a few accessibility activities – to encourage you to think about how people might interact with your applications without mouse or using a screen reader. To reflect on this process, please fill out the Accessibility Questionnaire.

Accessibility Research Study

We also wanted to invite you to participate in a research study – to examine and reflect on how to better teach students about accessibility within the software development process. Please fill out this Consent to participate in research form to let us know whether or not you are willing participate in this study. Participation is totally optional.

Appreciations

And while you’re at it, please take a moment to thank / write a note to a peer mentor who helped you in some way (even if you just take 30 seconds to do it). Peer mentors are students too, and most of them do waaaay more than what is officially asked of them in order to support you. This form is completely anonymous.

https://forms.gle/39oWerrVYaasgNd28

What to Turn In

Please review the requirements above and ensure you have met them. When you’re done, please submit the following to Canvas: