Skip to content

Kanary is a full stack, single-page clone of Kanopy, a video streaming service.

Notifications You must be signed in to change notification settings

rebeccamrfoster/Kanary

Repository files navigation

Kanary Live!

Table of Contents

Description

Kanary is a full stack, single-page web application inspired by Kanopy. It is a video streaming service that provides access to documentaries and independent films with a unique social, cultural, and political impact.

Technologies

  • React/Redux
  • Ruby on Rails
  • JavaScript, HTML, & SCSS
  • PostgreSQL
  • Node.js
  • jQuery
  • jBuilder
  • Webpack
  • Amazon Web Services S3

Kanary is built with the React framework and Redux architecture on the frontend, supported by Ruby on Rails and a PostgreSQL database on the backend. Thumbnails and videos are implemented using Active Storage and hosted by Amazon Web Services S3.

Core Features

Video Playback

I utilized Amazon Web Services S3 (AWS) and Rails Active Storage to store and fetch videos and images from the cloud, preloading videos upon entering the video show page to minimize wait time for end users.

Searchbar

The searchbar feature uses lifecycle methods and selector functions to enable users to search the site's selection of films by director, description, or genre.

In order to ensure that the search results update dynamically as users type in the search bar, the state for the SearchbarIndex component contains two key-value pairs: (1) the search query from the text input element and (2) an array containing movie objects that match the search query. Each time the search query is updated, the selector function selectMoviesBySearch is invoked to select for films that match the query. State is then updated with the returned array, and a rerender of the SearchbarIndex component is initiated.

handleUpdate(event) {
    const query = event.target.value;

    const matched = this.props.selectMoviesBySearch(
        this.props.movies, this.props.genres, query
    );

    this.setState({ query, movies: matched });
}

CHALLENGE:

Initially, if the user deleted their search query from the search bar, the selectMoviesBySearch function would select for movies whose director, description, or genre matched an empty string, which of course was all movies in the database. Furthermore, if the user submitted the form containing the input element on an empty search query string, the site would navigate to the search results page to display all those movies.

If the user submitted the form when the input element was populated with a valid search query, the site would successfully navigate to the search results page, but the search bar would remain populated with the user's latest query and a dropdown with the matching movies.

SOLUTION:

If the value of the input element becomes empty at any point, the component renders null rather than rendering all movies in the database. I also added a conditional statement to the onSubmit function so that the submit button is nonresponsive and the user remains on the current page if their search query is empty.

The onSubmit function also calls a handleClearSearchbar function. Upon submission of a valid search query, the search bar query is reset to the empty string and the dropdown becomes null and disappears.

<form className="searchbar-input" onSubmit={() => {
    if (this.state.query !== "") {
        this.props.history.push(`/search/${this.state.query}`);
        this.handleClearSearchbar();
    }
}}>
    <input type="text"
        placeholder="Search videos, subjects..."
        onChange={this.handleUpdate} />
    <button type="submit">
        <img src={window.search_icon_black} />
    </button>
</form>

Popup

Whenever a user adds or removes a film from their watchlist (via CRUD actions), a popup appears to inform them that the action was successful. The Popup component is constructed using React hooks to fade in when the user adds or removes a film and fade out when the user clicks anywhere on the popup, including the "X" button.

CHALLENGE:

Initially, when a user added or removed multiple films in succession from the MovieIndex page, only the popup for the first movie added or removed was visible, so the most recent action was not reflected by the information on the popup.

SOLUTION:

By adding a Boolean value called displayPopup to the state of the parent component, the Popup component is now conditionally rendered based on that value. The parent component passes down a clearPopup function to the Popup component. When invoked in the Popup component, clearPopup toggles the value of displayPopup to false in the parent, thus removing the previous popup from the page before rendering a new one to reflect the most recent user action.

{
    this.state.displayPopup ? (
        <Popup key={movie.id}
            added={this.state.icon === window.check_icon}
            title={movie.title} 
            clearPopup={this.clearPopup} />
    ) : null
}
const handleClearPopup = () => {
    const el = document.querySelector(".popup");
    el.id = "fade-out";
    setTimeout(() => {
        clearPopup();
    }, 600)
};