/seedfund

Seedfund is a Kickstarter clone demo site built with Python, Flask and React. Made in January 2021

Primary LanguagePython

SeedFund


Table of Contents




Description

A crowdfunding site in the style of Kickstarter. Currently, users can create projects and pledge money to ideas they like. Improvements and additional features to follow.





Application Architecture

SeedFund is a streamlined fullstack application built with PostgreSQL and Flask at the backend, which accepts RESTful data requests from the front-end user interface powered by React.


AppArchetecture




Frontend Overview


SeedFund was designed with simplicity in mind, allowing the frontend to focus on rendering served database records. However, future improvements such as messaging and project campaign rewards will make fuller use of React's powerful ability to maintain state.


React

React components simplify development and allow for the reuse of code while still keeping it dry. This is exemplified in SeedFund through the project card component. We have made use of this single page of code in different ways throughout the entire app.

The project card represents the benefit and disadvantages to using React all at once. Once challenge that we had to overcome was the split second flicker that can happen with React's useEffect hook. In this case, the app was rendering the default picture before the component could be updated with the picture from the database. There were several ways we could have addressed this problem but ultimately applied a loading animation to reveal the rendered component underneath after the DOM had been updated.


Framer Motion

To implement this solution, we brought in Framer Motion; It is an easy to use motion library that simplifies the work needed in CSS to achieve animation effects. We have posted the implementation of this library below:


loading animation component
import React, { useEffect, useRef } from "react";
import { motion } from "framer-motion";

const spinnerAnimation = {
  repeat: 2,
  repeatType: "loop",
  duration: 1.25,
  ease: "linear"
};

const LoadingAnimation = (props) => {

  const containerRef = useRef();
  const animationRef = useRef();

  const containerStyle = `spinnerContainer--${props.size}`;
  const animationStyle = `spinner--${props.size}`

  useEffect(() => {
    containerRef.current.classList.add(containerStyle);
    animationRef.current.classList.add(animationStyle);
  }, [])

  return (
    <div className="spinnerContainer" ref={containerRef}>
      <motion.span className="spinner"
        ref={animationRef}
        animate={{ rotate: 360}}
        transition={spinnerAnimation}
        />
    </div>
  );
};

export default LoadingAnimation;
Relevant CSS for component
.loadSpinner {
  position: absolute;
  transition: opacity 0.1s;
  height: 100%;
  width: 100%;
}

.loadSpinner--hide {
  opacity: 0;
}

.spinnerContainer  {
  position: relative;
  background-color: #FFFFFF;
  width: 100%;
  top: 0;
  left: 0;
  transition: opacity 0.1s;
}

.spinnerContainer--MED {
  height: 210px;
}

.spinner {
  display: block;
  border: solid #E8E8E8;
  border-top: solid #028858;
  border-radius: 50%;
  position: absolute;
  box-sizing: border-box;
}

.spinner--MED {
    top: 75px;
    left: 146px;
    width: 6rem;
    height: 6rem;
    border-width: 1rem;
}
Parent component implementation
    <>
      <div className="projectcard">
        <div>
          <div className="projectcard__wrapper">
            <div className="projectcard__container">
              <div className="projectcard__picturebox">
                <NavLink
                  to={"/project/" + project.id}
                  className="projectcard__picturebox-navlink"
                >
                  <div ref={spinnerRef} className="loadSpinner">
                    <LoadingAnimation size={"MED"} />
                  </div>
                  <div
                    style={
                      project.image
                        ? { backgroundImage: `url(${project.image})` }
                        : { backgroundImage: `url(${default_img})`}
                    }
                    className="projectcard__picture"
                  ></div>
                </NavLink>
              </div>
              <div>
                <div className="projectcard__topdata">
                  <div className="projectcard__topdata-text-container">
                    <NavLink
                      to={"/project/" + project.id}
                      className="projectcard__topdata-name"
                    >
                      <h3 className="projectcard__topdata-header">{project.title}</h3>
                      <p className="projectcard__topdata-desc">{project.description}</p>
                    </NavLink>
                  </div>
                </div>
                <div className="projectcard__topdata-creator">
                  <div style={{ display: "inline-block" }}>
                    <NavLink
                      to={{
                        pathname: "/discover/users/" + creator,
                        state: { creator_id: project.user_id },
                      }}
                      className="projectcard__topdata-creator-link"
                    >
                      <span>{"By " + creator}</span>
                    </NavLink>
                  </div>
                </div>
              </div>
              <div className="projectcard__bottomdata">
                <div className="projectcard__bottomdata-fillbar">
                  <div
                    className="projectcard__bottomdata-fillbar-progress"
                    style={fillBar(project.balance, project.funding_goal)}
                  ></div>
                </div>
                <div className="projectcard__bottomdata-campaign">
                  <div className="projectcard__bottomdata-pledged">
                    <span>{pledgeCount + " pledged"}</span>
                  </div>
                  <div className="projectcard__bottomdata-percent-funded">
                    <span>{funding}</span>
                  </div>
                  {remainingDays()}
                  <div className="projectcard__bottomdata-days">
                    <span className="projectcard__bottomdata-days-text">
                      {"End date: " + project.date_goal}
                    </span>
                  </div>
                  <div>
                    {project.category ? (
                      <NavLink
                        to={"/discover/" + project.category.toLowerCase()}
                        className="projectcard__bottomdata-category"
                      >
                        {project.category}
                      </NavLink>
                    ) : (
                        <NavLink
                          to={"#"}
                          className="projectcard__bottomdata-category"
                        >
                          Category
                        </NavLink>
                    )}
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </>

Backend Overview

SeedFund utilizes Python through the power of a Flask server with a PostgreSQL database to serve the front end. The entire operation is very straightforward with Flask facilitating data transfer to and from the user interface, updating data records in PostgreSQL as needed.

PostgreSQL

PostgreSQL's main draw is that it has 25 years of open source development behind it, making it one of the most stable and professional open source databases out there. Can't beat free and PostgreSQL is not lacking in features or power. It can handle high amounts of traffic which this app could certainly see, if it were in public use.

Flask React Project

This is the backend for the Flask React project.

Getting started

  1. Clone this repository (only this branch)

    git clone https://github.com/appacademy-starters/python-project-starter.git
  2. Install dependencies

    pipenv install --dev -r dev-requirements.txt && pipenv install -r requirements.txt
  3. Create a .env file based on the example with proper settings for your development environment

  4. Setup your PostgreSQL user, password and database and make sure it matches your .env file

  5. Get into your pipenv, migrate your database, seed your database, and run your flask app

    pipenv shell
    flask db upgrade
    flask seed all
    flask run
  6. To run the React App in development, checkout the README inside the react-app directory.


IMPORTANT! If you add any python dependencies to your pipfiles, you'll need to regenerate your requirements.txt before deployment. You can do this by running:

pipenv lock -r > requirements.txt

ALSO IMPORTANT! psycopg2-binary MUST remain a dev dependency because you can't install it on apline-linux. There is a layer in the Dockerfile that will install psycopg2 (not binary) for us.


Deploy to Heroku

  1. Create a new project on Heroku

  2. Under Resources click "Find more add-ons" and add the add on called "Heroku Postgres"

  3. Install the Heroku CLI

  4. Run

    heroku login
  5. Login to the heroku container registry

    heroku container:login
  6. Update the REACT_APP_BASE_URL variable in the Dockerfile. This should be the full URL of your Heroku app: i.e. "https://flask-react-aa.herokuapp.com"

  7. Push your docker container to heroku from the root directory of your project. This will build the dockerfile and push the image to your heroku container registry

    heroku container:push web -a {NAME_OF_HEROKU_APP}
  8. Release your docker container to heroku

    heroku container:release web -a {NAME_OF_HEROKU_APP}
  9. set up your database:

    heroku run -a {NAME_OF_HEROKU_APP} flask db upgrade
    heroku run -a {NAME_OF_HEROKU_APP} flask seed all
  10. Under Settings find "Config Vars" and add any additional/secret .env variables.

  11. profit

Flask

Flask is a micro framework that uses Python. This means it is light weight, easy to set up, and powerful. Flask's barebones approach allows you to not have to worry about bloat in your app and scale up as needed. This translates into a faster, more responsive application for your users.

A challenge that presented itself during this project was how the search route interacted with parameters passed from the front end. In particular, clicking on a quick select bar item that had an ampersand in it was causing a bad request error. Interestingly enough, the query parameter didn't even include an ampersand. Instead it was converting that into a space and making a fetch request to the backend for each word.

The front end loops were inefficient and it was unclear why this particular error was only happening with the quick select bar. If you typed the same query into the search bar, it would not produce an error. Our solution to this was to pass our query in a single fetch request with the '+' character standing in for any spaces. On the backend, we split the query and iterate through a list of parameters to return a dictionary of the combined results.


@project_routes.route('/search/<query>')
def searchForProjects(query):

    search_terms = query.split('+')

    result = list(chain.from_iterable(
                     (Project.query.filter(or_(
                         Project.title.ilike(f"%{term}%"),
                         Project.description.ilike(f"%{term}%"),
                         Project.category.ilike(f"%{term}%")
                     )).options(joinedload(Project.user)).all())
                     for term in search_terms))

    data = [project.to_dict() for project in result]

    return {"projects": data}

Conclusion and Next Steps

What we have built here is just the beginning. We have plans to add additional features to better reflect what crowdfunding applications can and should do.

Ultimately this was an enjoyable project that demonstrated just how easily and quickly the Flask/React stack can bring a job from concept to working software.




Team

Corbin Armendariz James Lee Miguel Munoz TJ Taylor
Corbin Armendariz James Lee Miguel Munoz TJ Taylor
github.com/corbinHA github.com/JamestLee513 github.com/memg92 github.com/tjtaylorjr