- Description
- Application Architecture & Technologies
- Frontend Overview
- Backend Overview
- Conclusion & Next Steps
- Team
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.
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.
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 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.
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:
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;
.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;
}
<>
<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>
</>
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'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.
This is the backend for the Flask React project.
-
Clone this repository (only this branch)
git clone https://github.com/appacademy-starters/python-project-starter.git
-
Install dependencies
pipenv install --dev -r dev-requirements.txt && pipenv install -r requirements.txt
-
Create a .env file based on the example with proper settings for your development environment
-
Setup your PostgreSQL user, password and database and make sure it matches your .env file
-
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
-
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.
-
Create a new project on Heroku
-
Under Resources click "Find more add-ons" and add the add on called "Heroku Postgres"
-
Install the Heroku CLI
-
Run
heroku login
-
Login to the heroku container registry
heroku container:login
-
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" -
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}
-
Release your docker container to heroku
heroku container:release web -a {NAME_OF_HEROKU_APP}
-
set up your database:
heroku run -a {NAME_OF_HEROKU_APP} flask db upgrade heroku run -a {NAME_OF_HEROKU_APP} flask seed all
-
Under Settings find "Config Vars" and add any additional/secret .env variables.
-
profit
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}
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.
Corbin Armendariz | James Lee | Miguel Munoz | TJ Taylor |
---|---|---|---|
github.com/corbinHA |
github.com/JamestLee513 |
github.com/memg92 |
github.com/tjtaylorjr |