├── .dockerignore ├── .env.example ├── .flaskenv ├── .gitignore ├── .vscode └── settings.json ├── Dockerfile ├── Pipfile ├── Pipfile.lock ├── README.md ├── app ├── __init__.py ├── api │ ├── area_routes.py │ ├── auth_routes.py │ ├── event_routes.py │ ├── item_routes.py │ ├── type_routes.py │ └── user_routes.py ├── config.py ├── forms │ ├── __init__.py │ ├── area_form.py │ ├── event_form.py │ ├── item_form.py │ ├── login_form.py │ └── signup_form.py ├── models │ ├── __init__.py │ ├── area.py │ ├── db.py │ ├── event.py │ ├── item.py │ ├── type.py │ └── user.py └── seeds │ ├── __init__.py │ ├── areas.py │ ├── events.py │ ├── items.py │ ├── types.py │ └── users.py ├── dev-requirements.txt ├── migrations ├── README ├── alembic.ini ├── env.py ├── script.py.mako └── versions │ ├── 20201120_150602_create_users_table.py │ └── 20210305_134652_.py ├── react-app ├── .env.example ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public │ ├── aboutImage.svg │ ├── beachLitter.jpg │ ├── carousel_1.svg │ ├── carousel_2.svg │ ├── carousel_3.svg │ ├── carousel_4.svg │ ├── carousel_5.svg │ ├── carousel_6.svg │ ├── favicon.ico │ ├── gitHubMark.png │ ├── index.html │ ├── land_logo.svg │ ├── linkedIn.png │ ├── logoWithName.png │ ├── logo_bckgrnd.svg │ ├── logo_beach.svg │ ├── logo_navbar.svg │ ├── logo_search.svg │ ├── markerLogo.svg │ ├── simpleLogo.png │ ├── simpleLogo.svg │ ├── trashBin.jpg │ ├── trashed_home.png │ └── urban_logo.svg └── src │ ├── App.js │ ├── components │ ├── About │ │ ├── About.css │ │ └── index.js │ ├── AreaCreate │ │ ├── AreaCreate.css │ │ └── index.js │ ├── AreaEventView │ │ ├── AreaEventView.css │ │ └── index.js │ ├── AreaView │ │ ├── AreaView.css │ │ └── index.js │ ├── DeleteAreaModal │ │ ├── Delete.js │ │ ├── DeleteAreaModal.css │ │ └── index.js │ ├── DeleteEventModal │ │ ├── Delete.js │ │ ├── DeleteEventModal.css │ │ └── index.js │ ├── DeleteItemModal │ │ ├── Delete.js │ │ ├── DeleteItemModal.css │ │ └── index.js │ ├── EditAreaView │ │ ├── EditAreaView.css │ │ └── index.js │ ├── EditEventView │ │ ├── EditEventView.css │ │ └── index.js │ ├── EditItemView │ │ ├── EditItemView.css │ │ └── index.js │ ├── EventCreate │ │ ├── EventCreate.css │ │ └── index.js │ ├── EventView │ │ ├── EventView.css │ │ └── index.js │ ├── Footer │ │ ├── Footer.css │ │ └── index.js │ ├── GoogleMap │ │ ├── GoogleMap.css │ │ ├── index.js │ │ └── mapStyles.js │ ├── HomePage │ │ ├── HomePage.css │ │ └── index.js │ ├── InfoWindowComponent │ │ ├── InfoWindow.css │ │ └── index.js │ ├── ItemCreate │ │ ├── ItemCreate.css │ │ └── index.js │ ├── ItemView │ │ ├── ItemView.css │ │ └── index.js │ ├── NavBar.css │ ├── NavBar.js │ ├── SearchBar │ │ ├── SearchBar.css │ │ └── index.js │ ├── SearchResult │ │ ├── SearchResult.css │ │ └── index.js │ ├── SingleItemView │ │ ├── SingleItemView.css │ │ └── index.js │ ├── TypeView │ │ ├── TypeView.css │ │ └── index.js │ ├── User.js │ ├── UsersList.js │ └── auth │ │ ├── LoginForm.js │ │ ├── LogoutButton.css │ │ ├── LogoutButton.js │ │ ├── ProtectedRoute.js │ │ ├── SignUpForm.css │ │ ├── SignUpForm.js │ │ └── SplashView.css │ ├── context │ ├── Modal.css │ └── Modal.js │ ├── index.css │ ├── index.js │ ├── services │ └── auth.js │ └── store │ ├── area.js │ ├── event.js │ ├── index.js │ ├── item.js │ ├── session.js │ ├── type.js │ └── user.js └── requirements.txt /.dockerignore: -------------------------------------------------------------------------------- 1 | react-app/node_modules 2 | .venv 3 | Pipfile 4 | Pipfile.lock 5 | .env 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | FLASK_APP=app 2 | FLASK_ENV=development 3 | SECRET_KEY=lkasjdf09ajsdkfljalsiorj12n3490re9485309irefvn,u90818734902139489230 4 | DATABASE_URL=postgresql://starter_app_dev@localhost/starter_app 5 | -------------------------------------------------------------------------------- /.flaskenv: -------------------------------------------------------------------------------- 1 | FLASK_APP=app -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | __pycache__/ 3 | *.py[cod] 4 | .venv 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.pythonPath": "/home/echo/dev/appacademy/starters/python-project-starter/.venv/bin/python", 3 | "python.linting.pylintEnabled": false, 4 | "python.linting.enabled": true, 5 | "python.linting.pycodestyleEnabled": true 6 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12 AS build-stage 2 | 3 | WORKDIR /react-app 4 | COPY react-app/. . 5 | 6 | # You have to set this because it should be set during build time. 7 | ENV REACT_APP_BASE_URL=https://trash-ed.herokuapp.com/ 8 | 9 | # Build our React App 10 | RUN npm install 11 | RUN npm run build 12 | 13 | FROM python:3.8 14 | 15 | # Setup Flask environment 16 | ENV FLASK_APP=app 17 | ENV FLASK_ENV=production 18 | ENV SQLALCHEMY_ECHO=True 19 | 20 | EXPOSE 8000 21 | 22 | WORKDIR /var/www 23 | COPY . . 24 | COPY --from=build-stage /react-app/build/* app/static/ 25 | 26 | # Install Python Dependencies 27 | RUN pip install -r requirements.txt 28 | RUN pip install psycopg2 29 | 30 | # Run flask environment 31 | CMD gunicorn app:app 32 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | click = "==7.1.2" 8 | gunicorn = "==20.0.4" 9 | itsdangerous = "==1.1.0" 10 | python-dotenv = "==0.14.0" 11 | six = "==1.15.0" 12 | Flask = "==1.1.2" 13 | Flask-Cors = "==3.0.8" 14 | Flask-SQLAlchemy = "==2.4.4" 15 | Flask-WTF = "==0.14.3" 16 | Jinja2 = "==2.11.2" 17 | MarkupSafe = "==1.1.1" 18 | SQLAlchemy = "==1.3.19" 19 | Werkzeug = "==1.0.1" 20 | WTForms = "==2.3.3" 21 | Flask-JWT-Extended = "==3.24.1" 22 | email-validator = "*" 23 | Flask-Migrate = "==2.5.3" 24 | Flask-Login = "==0.5.0" 25 | alembic = "==1.4.3" 26 | python-dateutil = "==2.8.1" 27 | python-editor = "==1.0.4" 28 | Mako = "==1.1.3" 29 | PyJWT = "==1.7.1" 30 | 31 | [dev-packages] 32 | psycopg2-binary = "==2.8.6" 33 | autopep8 = "*" 34 | pylint = "*" 35 | 36 | [requires] 37 | python_version = "3.8" 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Untitled-drawing-4.png](https://i.postimg.cc/L5pzcjJj/Untitled-drawing-4.png)](https://postimg.cc/cgFgYgMJ) 2 | 3 | ## Trashed is an organizational tool for users who want to take ownership in keeping their communities clean. 4 | 5 |

Try the site live | Check out the Wiki | Visit the developers portfolio

6 | 7 | [![Screen-Shot-2021-03-23-at-6-38-18-PM.png](https://i.postimg.cc/DfxBbYSk/Screen-Shot-2021-03-23-at-6-38-18-PM.png)](https://postimg.cc/z3Rns7w0) 8 | 9 | ## Technologies used in Trashed 10 | 11 | **JavaScript** | **Python** | **SQLAlchemy** | **Flask** | **React** | **Redux** 12 | 13 | **Google Maps API** | **Geocode API** | **HTML** | **CSS** | **Docker** | **Heroku** 14 | 15 | ## Features implemented 16 | 17 | * Users can **log in** or **sign up** to access the site. 18 | * A user has the ability to **report a trashed area** that displays on Google Map. 19 | * A user has the ability to **organize clean up events** for a trashed area. 20 | * The **search** bar can locate using a case insensitive search term and display results to the user. 21 | * A user has the ability to **post tips** to share different ways to reduce waste at home. 22 | * A user can only **edit** an area, event, or tip that they created. 23 | * A user can only **delete** an event or tip that they created. 24 | 25 | 26 | ## Challenges 27 | It was a challenge deciding how to allow users to interact with a map and add markers to the map that didn't require them to know the latitude and longitude of the location they wished to add. After research and looking at documentation, I decided to implement a Geocode API to obtain the latitude and longitude of each created area. Another challenging aspect of this feature was finding a way to use Geocode that would send back the latitude and longitude before the post request was sent to the back end. For my solution, I used two helper functions (one for latitude and one for longitude) that utilized **Geocode.fromAddress()** and passed in an interpolated string that made up the entire address provided by the user. From there I was able to call each helper function and await the results inside of an asynchronous **handleSubmit()** before it was sent to the appropriate thunk. 28 | 29 | [![Screen-Shot-2021-03-23-at-6-36-47-PM.png](https://i.postimg.cc/X78GvTqd/Screen-Shot-2021-03-23-at-6-36-47-PM.png)](https://postimg.cc/WhdbWYM1) 30 | 31 | ## Getting started 32 | 33 | 1. Clone the repository 34 | 35 | ```bash 36 | git clone https://github.com/QuintinHull/trashed.git 37 | ``` 38 | 39 | 2. Install dependencies 40 | 41 | ```bash 42 | pipenv install --dev -r dev-requirements.txt && pipenv install -r requirements.txt 43 | ``` 44 | 45 | 3. Create a **.env** file based on the example with proper settings for your 46 | development environment 47 | 4. Setup your PostgreSQL user, password and database and make sure it matches your **.env** file 48 | 49 | 5. Get into your pipenv, migrate your database, seed your database, and run your flask app 50 | 51 | ```bash 52 | pipenv shell 53 | ``` 54 | 55 | ```bash 56 | flask db upgrade 57 | ``` 58 | 59 | ```bash 60 | flask seed all 61 | ``` 62 | 63 | ```bash 64 | flask run 65 | ``` 66 | 67 | 6. To run the React App in development, checkout the [README](./react-app/README.md) inside the `react-app` directory. 68 | 69 | *** 70 | *IMPORTANT!* 71 | If you add any python dependencies to your pipfiles, you'll need to regenerate your requirements.txt before deployment. 72 | You can do this by running: 73 | 74 | ```bash 75 | pipenv lock -r > requirements.txt 76 | ``` 77 | 78 | *ALSO IMPORTANT!* 79 | psycopg2-binary MUST remain a dev dependency because you can't install it on apline-linux. 80 | There is a layer in the Dockerfile that will install psycopg2 (not binary) for you. 81 | *** 82 | 83 | ## Deploy to Heroku 84 | 85 | 1. Create a new project on Heroku 86 | 2. Under Resources click "Find more add-ons" and add the add on called "Heroku Postgres" 87 | 3. Install the [Heroku CLI](https://devcenter.heroku.com/articles/heroku-command-line) 88 | 4. Run 89 | 90 | ```bash 91 | heroku login 92 | ``` 93 | 94 | 5. Login to the heroku container registry 95 | 96 | ```bash 97 | heroku container:login 98 | ``` 99 | 100 | 6. Update the `REACT_APP_BASE_URL` variable in the Dockerfile. 101 | This should be the full URL of your Heroku app: i.e. "https://flask-react-aa.herokuapp.com" 102 | 7. Push your docker container to heroku from the root directory of your project. 103 | This will build the dockerfile and push the image to your heroku container registry 104 | 105 | ```bash 106 | heroku container:push web -a {NAME_OF_HEROKU_APP} 107 | ``` 108 | 109 | 8. Release your docker container to heroku 110 | 111 | ```bash 112 | heroku container:release web -a {NAME_OF_HEROKU_APP} 113 | ``` 114 | 115 | 9. set up your database: 116 | 117 | ```bash 118 | heroku run -a {NAME_OF_HEROKU_APP} flask db upgrade 119 | heroku run -a {NAME_OF_HEROKU_APP} flask seed all 120 | ``` 121 | 122 | 10. Under Settings find "Config Vars" and add any additional/secret .env variables. 123 | 124 | 125 | # Thanks for reading my README! 126 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | from flask import Flask, render_template, request, session, redirect 3 | from flask_cors import CORS 4 | from flask_migrate import Migrate 5 | from flask_wtf.csrf import CSRFProtect, generate_csrf 6 | from flask_login import LoginManager 7 | 8 | from .models import db, User 9 | from .api.user_routes import user_routes 10 | from .api.auth_routes import auth_routes 11 | from .api.area_routes import area_routes 12 | from .api.event_routes import event_routes 13 | from .api.type_routes import type_routes 14 | from .api.item_routes import item_routes 15 | 16 | from .seeds import seed_commands 17 | 18 | from .config import Config 19 | 20 | app = Flask(__name__) 21 | 22 | # Setup login manager 23 | login = LoginManager(app) 24 | login.login_view = 'auth.unauthorized' 25 | 26 | 27 | @login.user_loader 28 | def load_user(id): 29 | return User.query.get(int(id)) 30 | 31 | 32 | # Tell flask about our seed commands 33 | app.cli.add_command(seed_commands) 34 | 35 | app.config.from_object(Config) 36 | app.register_blueprint(user_routes, url_prefix='/api/users') 37 | app.register_blueprint(auth_routes, url_prefix='/api/auth') 38 | app.register_blueprint(area_routes, url_prefix='/api/areas') 39 | app.register_blueprint(event_routes, url_prefix='/api/events') 40 | app.register_blueprint(type_routes, url_prefix='/api/types') 41 | app.register_blueprint(item_routes, url_prefix='/api/items') 42 | 43 | db.init_app(app) 44 | Migrate(app, db) 45 | 46 | # Application Security 47 | CORS(app) 48 | 49 | # Since we are deploying with Docker and Flask, 50 | # we won't be using a buildpack when we deploy to Heroku. 51 | # Therefore, we need to make sure that in production any 52 | # request made over http is redirected to https. 53 | # Well......... 54 | 55 | @app.before_request 56 | def https_redirect(): 57 | if os.environ.get('FLASK_ENV') == 'production': 58 | if request.headers.get('X-Forwarded-Proto') == 'http': 59 | url = request.url.replace('http://', 'https://', 1) 60 | code = 301 61 | return redirect(url, code=code) 62 | 63 | 64 | @app.after_request 65 | def inject_csrf_token(response): 66 | response.set_cookie('csrf_token', 67 | generate_csrf(), 68 | secure=True if os.environ.get( 69 | 'FLASK_ENV') == 'production' else False, 70 | samesite='Strict' if os.environ.get( 71 | 'FLASK_ENV') == 'production' else None, 72 | httponly=True) 73 | return response 74 | 75 | 76 | @app.route('/', defaults={'path': ''}) 77 | @app.route('/') 78 | def react_root(path): 79 | print("path", path) 80 | if path == 'favicon.ico': 81 | return app.send_static_file('favicon.ico') 82 | return app.send_static_file('index.html') 83 | -------------------------------------------------------------------------------- /app/api/area_routes.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from flask import Blueprint, jsonify, request, Response 3 | from flask_login import current_user 4 | from app.models import db, Area 5 | from app.forms import AreaForm 6 | from sqlalchemy import and_ 7 | 8 | area_routes = Blueprint("areas", __name__) 9 | 10 | @area_routes.route("/") 11 | def all_areas(): 12 | areas = Area.query.all() 13 | # areas = Area.query.order_by(Area.id) 14 | # areas = Area.query.all().order_by(Area.id) 15 | return {"all_areas": {area.id: area.to_dict() for area in areas}} 16 | 17 | @area_routes.route("/") 18 | def single_area(id): 19 | area = Area.query.get(id) 20 | return {"area": area.to_dict()} 21 | 22 | @area_routes.route("/", methods=["POST"]) 23 | def create_area(): 24 | form = AreaForm() 25 | form['csrf_token'].data = request.cookies['csrf_token'] 26 | if form.validate_on_submit(): 27 | area = Area( 28 | address=form.data["address"], 29 | city=form.data['city'], 30 | state=form.data['state'], 31 | zipcode=form.data['zipcode'], 32 | description=form.data['description'], 33 | clean=False, 34 | latitude=form.data['latitude'], 35 | longitude=form.data['longitude'], 36 | created_at=datetime.datetime.now(), 37 | user_id=current_user.id, 38 | ) 39 | db.session.add(area) 40 | db.session.commit() 41 | return {"area": area.to_dict()} 42 | return {"errors": "error with area form / post route"} 43 | 44 | @area_routes.route("//edit", methods=["PUT"]) 45 | def edit_location(id): 46 | area = Area.query.get(id) 47 | 48 | new_area = request.get_json() 49 | 50 | area.address = new_area["address"] 51 | area.city = new_area["city"] 52 | area.state = new_area["state"] 53 | area.zipcode = new_area["zipcode"] 54 | area.description = new_area["description"] 55 | area.latitude = new_area["latitude"] 56 | area.longitude = new_area["longitude"] 57 | 58 | db.session.commit() 59 | return {"area": area.to_dict()} 60 | 61 | 62 | @area_routes.route('/delete/', methods=["DELETE"]) 63 | def delete_area(id): 64 | area = Area.query.get(id) 65 | db.session.delete(area) 66 | db.session.commit() 67 | return {'area': area.to_dict()} 68 | 69 | 70 | @area_routes.route('/search/') 71 | def search_areas(city): 72 | areas = Area.query.filter(Area.city.ilike(f'%{city}%')) 73 | return {"searched_areas": [area.to_dict() for area in areas]} 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /app/api/auth_routes.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, jsonify, session, request 2 | from app.models import User, db 3 | from app.forms import LoginForm 4 | from app.forms import SignUpForm 5 | from flask_login import current_user, login_user, logout_user, login_required 6 | 7 | auth_routes = Blueprint('auth', __name__) 8 | 9 | 10 | def validation_errors_to_error_messages(validation_errors): 11 | """ 12 | Simple function that turns the WTForms validation errors into a simple list 13 | """ 14 | errorMessages = [] 15 | for field in validation_errors: 16 | for error in validation_errors[field]: 17 | errorMessages.append(f"{field} : {error}") 18 | return errorMessages 19 | 20 | 21 | @auth_routes.route('/') 22 | def authenticate(): 23 | """ 24 | Authenticates a user. 25 | """ 26 | if current_user.is_authenticated: 27 | return current_user.to_dict() 28 | return {'errors': ['Unauthorized']}, 401 29 | 30 | 31 | @auth_routes.route('/login', methods=['POST']) 32 | def login(): 33 | """ 34 | Logs a user in 35 | """ 36 | form = LoginForm() 37 | print(request.get_json()) 38 | # Get the csrf_token from the request cookie and put it into the 39 | # form manually to validate_on_submit can be used 40 | form['csrf_token'].data = request.cookies['csrf_token'] 41 | if form.validate_on_submit(): 42 | # Add the user to the session, we are logged in! 43 | user = User.query.filter(User.email == form.data['email']).first() 44 | login_user(user) 45 | return user.to_dict() 46 | return {'errors': validation_errors_to_error_messages(form.errors)}, 401 47 | 48 | 49 | @auth_routes.route('/logout') 50 | def logout(): 51 | """ 52 | Logs a user out 53 | """ 54 | logout_user() 55 | return {'message': 'User logged out'} 56 | 57 | 58 | @auth_routes.route('/signup', methods=['POST']) 59 | def sign_up(): 60 | """ 61 | Creates a new user and logs them in 62 | """ 63 | form = SignUpForm() 64 | form['csrf_token'].data = request.cookies['csrf_token'] 65 | if form.validate_on_submit(): 66 | user = User( 67 | first_name=form.data['first_name'], 68 | last_name=form.data['last_name'], 69 | email=form.data['email'], 70 | password=form.data['password'] 71 | ) 72 | db.session.add(user) 73 | db.session.commit() 74 | login_user(user) 75 | return user.to_dict() 76 | return {'errors': validation_errors_to_error_messages(form.errors)} 77 | 78 | 79 | @auth_routes.route('/unauthorized') 80 | def unauthorized(): 81 | """ 82 | Returns unauthorized JSON when flask-login authentication fails 83 | """ 84 | return {'errors': ['Unauthorized']}, 401 85 | -------------------------------------------------------------------------------- /app/api/event_routes.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, jsonify, request, Response 2 | from flask_login import current_user 3 | from app.models import db, Event 4 | from app.forms import EventForm 5 | from sqlalchemy import and_ 6 | 7 | event_routes = Blueprint("events", __name__) 8 | 9 | 10 | @event_routes.route("/") 11 | def all_events(): 12 | events = Event.query.all() 13 | return {"all_events": {event.id: event.to_dict() for event in events}} 14 | 15 | @event_routes.route("/area/") 16 | def events_for_area(id): 17 | events = Event.query.filter(Event.area_id == id).all() 18 | return {"all_area_events": {event.id: event.to_dict() for event in events}} 19 | 20 | 21 | @event_routes.route("/") 22 | def single_event(id): 23 | event = Event.query.get(id) 24 | return {"event": event.to_dict()} 25 | 26 | 27 | @event_routes.route("/", methods=["POST"]) 28 | def create_event(areaId): 29 | form = EventForm() 30 | print(request.data) 31 | 32 | print("---------------form datetime------->", form.data['date_time']) 33 | 34 | form['csrf_token'].data = request.cookies['csrf_token'] 35 | if form.validate_on_submit(): 36 | event = Event( 37 | title=form.data['title'], 38 | date_time=form.data['date_time'], 39 | description=form.data['description'], 40 | area_id=areaId, 41 | user_id=current_user.id, 42 | ) 43 | print("---event route, event--->", event) 44 | db.session.add(event) 45 | db.session.commit() 46 | return {"event": event.to_dict()} 47 | return {"errors": "error with event form validation"} 48 | 49 | 50 | @event_routes.route("//edit", methods=["PUT"]) 51 | def edit_location(id): 52 | event = Event.query.get(id) 53 | 54 | new_event = request.get_json() 55 | 56 | event.title = new_event["title"] 57 | event.date_time = new_event["date_time"] 58 | event.description = new_event["description"] 59 | 60 | db.session.commit() 61 | return {"event": event.to_dict()} 62 | 63 | 64 | @event_routes.route('/delete/', methods=["DELETE"]) 65 | def delete_event(id): 66 | event = Event.query.get(id) 67 | db.session.delete(event) 68 | db.session.commit() 69 | return {'event': event.to_dict()} -------------------------------------------------------------------------------- /app/api/item_routes.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, jsonify, request, Response 2 | from flask_login import current_user 3 | from app.models import db, Item 4 | from app.forms import ItemForm 5 | 6 | item_routes = Blueprint("items", __name__) 7 | 8 | @item_routes.route("/") 9 | def all_items(): 10 | items = Item.query.all() 11 | return {"all_items": {item.id: item.to_dict() for item in items}} 12 | 13 | @item_routes.route("/type/") 14 | def items_for_type(id): 15 | items = Item.query.filter(Item.type_id == id).all() 16 | return {"all_type_items": {item.id: item.to_dict() for item in items}} 17 | 18 | @item_routes.route("/") 19 | def single_item(id): 20 | item = Item.query.get(id) 21 | return {"item": item.to_dict()} 22 | 23 | 24 | @item_routes.route("/", methods=["POST"]) 25 | def create_item(typeId): 26 | form = ItemForm() 27 | form['csrf_token'].data = request.cookies['csrf_token'] 28 | if form.validate_on_submit(): 29 | item = Item( 30 | name=form.data['name'], 31 | description=form.data['description'], 32 | user_id=current_user.id, 33 | type_id=typeId, 34 | ) 35 | db.session.add(item) 36 | db.session.commit() 37 | return {"item": item.to_dict()} 38 | return {"errors": "error with item form / post route"} 39 | 40 | 41 | @item_routes.route("//edit", methods=["PUT"]) 42 | def edit_item(id): 43 | item = Item.query.get(id) 44 | 45 | new_item = request.get_json() 46 | 47 | item.name = new_item["name"] 48 | item.description = new_item["description"] 49 | 50 | db.session.commit() 51 | return {"item": item.to_dict()} 52 | 53 | 54 | @item_routes.route('/delete/', methods=["DELETE"]) 55 | def delete_item(id): 56 | item = Item.query.get(id) 57 | db.session.delete(item) 58 | db.session.commit() 59 | return {'item': item.to_dict()} 60 | -------------------------------------------------------------------------------- /app/api/type_routes.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, jsonify, request, Response 2 | from app.models import db, Type 3 | 4 | type_routes = Blueprint("types", __name__) 5 | 6 | @type_routes.route("/") 7 | def all_types(): 8 | types = Type.query.all() 9 | return {"all_types": {one_type.id: one_type.to_dict() for one_type in types}} 10 | 11 | 12 | @type_routes.route("/") 13 | def single_type(id): 14 | one_type = Type.query.get(id) 15 | return {"one_type": one_type.to_dict()} -------------------------------------------------------------------------------- /app/api/user_routes.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, jsonify 2 | from flask_login import login_required 3 | from app.models import User 4 | 5 | user_routes = Blueprint('users', __name__) 6 | 7 | 8 | @user_routes.route('/') 9 | @login_required 10 | def users(): 11 | users = User.query.all() 12 | return {"users": [user.to_dict() for user in users]} 13 | 14 | 15 | @user_routes.route('/') 16 | @login_required 17 | def user(id): 18 | user = User.query.get(id) 19 | return user.to_dict() 20 | -------------------------------------------------------------------------------- /app/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | class Config: 4 | SECRET_KEY=os.environ.get('SECRET_KEY') 5 | SQLALCHEMY_TRACK_MODIFICATIONS=False 6 | SQLALCHEMY_DATABASE_URI=os.environ.get('DATABASE_URL') 7 | SQLALCHEMY_ECHO=True -------------------------------------------------------------------------------- /app/forms/__init__.py: -------------------------------------------------------------------------------- 1 | from .login_form import LoginForm 2 | from .signup_form import SignUpForm 3 | from .area_form import AreaForm 4 | from .event_form import EventForm 5 | from .item_form import ItemForm -------------------------------------------------------------------------------- /app/forms/area_form.py: -------------------------------------------------------------------------------- 1 | from flask_wtf import FlaskForm 2 | from wtforms import StringField, TextAreaField, IntegerField, SelectField, FloatField, BooleanField, DateTimeField 3 | from wtforms.validators import DataRequired 4 | from app.models import Area 5 | 6 | state = [("HI"), ("AL"), ("AK"), ("AZ"), ("AR"), ("CA"), ("CO"), ("CT"), ("DE"), ("FL"), ("GA"), ("ID"), ("IL"), ("IN"), ("IA"), ("KS"), ("KY"), ("LA"), ("ME"), ("MD"), ("MA"), ("MI"), ("MN"), ("MS"), 7 | ("MO"), ("MT"), ("NE"), ("NV"), ("NH"), ("NJ"), ("NM"), ("NY"), ("NC"), ("ND"), ("OH"), ("OK"), ("OR"), ("PA"), ("RI"), ("SC"), ("SD"), ("TN"), ("TX"), ("UT"), ("VT"), ("VA"), ("WA"), ("WV"), ("WI"), ("WY")] 8 | 9 | 10 | class AreaForm(FlaskForm): 11 | address = StringField("address", validators=[DataRequired()]) 12 | city = StringField("city", validators=[DataRequired()]) 13 | state = SelectField("state", choices=state, validators=[DataRequired()]) 14 | zipcode = IntegerField("zipcode", validators=[DataRequired()]) 15 | description = TextAreaField("description", validators=[DataRequired()]) 16 | latitude = FloatField("latitude") 17 | longitude = FloatField("longitude") -------------------------------------------------------------------------------- /app/forms/event_form.py: -------------------------------------------------------------------------------- 1 | from flask_wtf import FlaskForm 2 | from wtforms import StringField, TextAreaField 3 | from wtforms.fields.html5 import DateTimeLocalField 4 | from wtforms.validators import DataRequired 5 | from app.models import Event 6 | 7 | class EventForm(FlaskForm): 8 | title = StringField("title", validators=[DataRequired()]) 9 | date_time = DateTimeLocalField("date_time", validators=[DataRequired()], format="%Y-%m-%dT%H:%M") 10 | description = TextAreaField("description", validators=[DataRequired()]) -------------------------------------------------------------------------------- /app/forms/item_form.py: -------------------------------------------------------------------------------- 1 | from flask_wtf import FlaskForm 2 | from wtforms import StringField, TextAreaField 3 | from wtforms.validators import DataRequired 4 | from app.models import Item 5 | 6 | class ItemForm(FlaskForm): 7 | name = StringField("name", validators=[DataRequired()]) 8 | description = TextAreaField("description", validators=[DataRequired()]) 9 | 10 | -------------------------------------------------------------------------------- /app/forms/login_form.py: -------------------------------------------------------------------------------- 1 | from flask_wtf import FlaskForm 2 | from wtforms import StringField 3 | from wtforms.validators import DataRequired, Email, ValidationError 4 | from app.models import User 5 | 6 | 7 | def user_exists(form, field): 8 | print("Checking if user exists", field.data) 9 | email = field.data 10 | user = User.query.filter(User.email == email).first() 11 | if not user: 12 | raise ValidationError("Email provided not found.") 13 | 14 | 15 | def password_matches(form, field): 16 | print("Checking if password matches") 17 | password = field.data 18 | email = form.data['email'] 19 | user = User.query.filter(User.email == email).first() 20 | if not user: 21 | raise ValidationError("No such user exists.") 22 | if not user.check_password(password): 23 | raise ValidationError("Password was incorrect.") 24 | 25 | 26 | class LoginForm(FlaskForm): 27 | email = StringField('email', validators=[DataRequired(), user_exists]) 28 | password = StringField('password', validators=[ 29 | DataRequired(), password_matches]) 30 | -------------------------------------------------------------------------------- /app/forms/signup_form.py: -------------------------------------------------------------------------------- 1 | from flask_wtf import FlaskForm 2 | from wtforms import StringField 3 | from wtforms.validators import DataRequired, Email, ValidationError 4 | from app.models import User 5 | 6 | 7 | def user_exists(form, field): 8 | print("Checking if user exits", field.data) 9 | email = field.data 10 | user = User.query.filter(User.email == email).first() 11 | if user: 12 | raise ValidationError("User is already registered.") 13 | 14 | 15 | class SignUpForm(FlaskForm): 16 | first_name = StringField('first_name', validators=[DataRequired()]) 17 | last_name = StringField('last_name', validators=[DataRequired()]) 18 | email = StringField('email', validators=[DataRequired(), user_exists]) 19 | password = StringField('password', validators=[DataRequired()]) 20 | -------------------------------------------------------------------------------- /app/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .db import db 2 | from .user import User 3 | from .area import Area 4 | from .event import Event 5 | from .item import Item 6 | from .type import Type -------------------------------------------------------------------------------- /app/models/area.py: -------------------------------------------------------------------------------- 1 | from .db import db 2 | 3 | 4 | class Area(db.Model): 5 | __tablename__ = 'areas' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | address = db.Column(db.String(50), nullable=False) 9 | city = db.Column(db.String(50), nullable=False) 10 | state = db.Column(db.String(2), nullable=False) 11 | zipcode = db.Column(db.Integer, nullable=False) 12 | description = db.Column(db.String(250), nullable=False) 13 | clean = db.Column(db.Boolean, nullable=False) 14 | latitude = db.Column(db.Float) 15 | longitude = db.Column(db.Float) 16 | created_at = db.Column(db.DateTime, nullable=False) 17 | user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False) 18 | 19 | area_user = db.relationship("User", back_populates="user_area") 20 | area_event = db.relationship("Event", cascade="all, delete-orphan", back_populates="event_area") 21 | 22 | def to_dict(self): 23 | return { 24 | "id": self.id, 25 | "address": self.address, 26 | "city": self.city, 27 | "state": self.state, 28 | "zipcode": self.zipcode, 29 | "description": self.description, 30 | "clean": self.clean, 31 | "latitude": self.latitude, 32 | "longitude": self.longitude, 33 | "created_at": self.created_at, 34 | "user_id": self.user_id, 35 | "first_name": self.area_user.first_name, 36 | "last_name": self.area_user.last_name 37 | } -------------------------------------------------------------------------------- /app/models/db.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | db = SQLAlchemy() 3 | -------------------------------------------------------------------------------- /app/models/event.py: -------------------------------------------------------------------------------- 1 | from .db import db 2 | 3 | 4 | class Event(db.Model): 5 | __tablename__ = 'events' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | title = db.Column(db.String(50), nullable=False) 9 | date_time = db.Column(db.DateTime, nullable=False) 10 | description = db.Column(db.String(250), nullable=False) 11 | area_id = db.Column(db.Integer, db.ForeignKey("areas.id"), nullable=False) 12 | user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False) 13 | 14 | event_area = db.relationship("Area", back_populates="area_event") 15 | event_user = db.relationship("User", back_populates="user_event") 16 | 17 | def to_dict(self): 18 | return { 19 | "id": self.id, 20 | "title": self.title, 21 | "date_time": self.date_time, 22 | "description": self.description, 23 | "area_id": self.area_id, 24 | "user_id": self.user_id, 25 | "area_address": self.event_area.address, 26 | "area_state": self.event_area.state, 27 | "first_name": self.event_user.first_name, 28 | "last_name": self.event_user.last_name 29 | } 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/models/item.py: -------------------------------------------------------------------------------- 1 | from .db import db 2 | 3 | class Item(db.Model): 4 | __tablename__ = 'items' 5 | 6 | id = db.Column(db.Integer, primary_key=True) 7 | name = db.Column(db.String(50), nullable=False) 8 | description = db.Column(db.String(250), nullable=False) 9 | user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False) 10 | type_id = db.Column(db.Integer, db.ForeignKey("types.id"), nullable=False) 11 | 12 | item_user = db.relationship("User", back_populates="user_item") 13 | item_type = db.relationship("Type", back_populates="type_item") 14 | 15 | def to_dict(self): 16 | return { 17 | "id": self.id, 18 | "name": self.name, 19 | "description": self.description, 20 | "user_id": self.user_id, 21 | "type_id": self.type_id, 22 | "type": self.item_type.name, 23 | "first_name": self.item_user.first_name, 24 | "last_name": self.item_user.last_name, 25 | } 26 | -------------------------------------------------------------------------------- /app/models/type.py: -------------------------------------------------------------------------------- 1 | from .db import db 2 | 3 | 4 | class Type(db.Model): 5 | __tablename__ = "types" 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(50), nullable=False) 9 | 10 | type_item = db.relationship("Item", back_populates="item_type") 11 | 12 | def to_dict(self): 13 | return { 14 | "id": self.id, 15 | "name": self.name 16 | } -------------------------------------------------------------------------------- /app/models/user.py: -------------------------------------------------------------------------------- 1 | from .db import db 2 | from werkzeug.security import generate_password_hash, check_password_hash 3 | from flask_login import UserMixin 4 | 5 | class User(db.Model, UserMixin): 6 | __tablename__ = 'users' 7 | 8 | id = db.Column(db.Integer, primary_key = True) 9 | first_name = db.Column(db.String(50), nullable=False) 10 | last_name = db.Column(db.String(50), nullable=False) 11 | email = db.Column(db.String(255), nullable=False, unique=True) 12 | hashed_password = db.Column(db.String(255), nullable=False) 13 | 14 | # relationships here 15 | user_area = db.relationship("Area", back_populates="area_user") 16 | user_event = db.relationship("Event", back_populates="event_user") 17 | user_item = db.relationship("Item", back_populates="item_user") 18 | 19 | @property 20 | def password(self): 21 | return self.hashed_password 22 | 23 | 24 | @password.setter 25 | def password(self, password): 26 | self.hashed_password = generate_password_hash(password) 27 | 28 | 29 | def check_password(self, password): 30 | return check_password_hash(self.password, password) 31 | 32 | 33 | def to_dict(self): 34 | return { 35 | "id": self.id, 36 | "first_name": self.first_name, 37 | "last_name": self.last_name, 38 | "email": self.email 39 | } 40 | -------------------------------------------------------------------------------- /app/seeds/__init__.py: -------------------------------------------------------------------------------- 1 | from flask.cli import AppGroup 2 | from .users import seed_users, undo_users 3 | from .areas import seed_areas, undo_areas 4 | from .events import seed_events, undo_events 5 | from .types import seed_types, undo_types 6 | from .items import seed_items, undo_items 7 | 8 | # Creates a seed group to hold our commands 9 | # So we can type `flask seed --help` 10 | seed_commands = AppGroup('seed') 11 | 12 | # Creates the `flask seed all` command 13 | @seed_commands.command('all') 14 | def seed(): 15 | seed_users() 16 | seed_areas() 17 | seed_events() 18 | seed_types() 19 | seed_items() 20 | # Add other seed functions here 21 | 22 | # Creates the `flask seed undo` command 23 | @seed_commands.command('undo') 24 | def undo(): 25 | undo_users() 26 | undo_areas() 27 | undo_events() 28 | undo_types() 29 | undo_items() 30 | # Add other undo functions here 31 | -------------------------------------------------------------------------------- /app/seeds/areas.py: -------------------------------------------------------------------------------- 1 | from app.models import db, Area 2 | 3 | def seed_areas(): 4 | 5 | area_1 = Area(address="White Plains", city="Ewa Beach", state="HI", zipcode="96706", description="White Plains Beach", clean=False, latitude=21.3035, 6 | longitude=-158.0452, created_at="2021-03-03 04:05:06", user_id="1") 7 | area_2 = Area(address="91-1450 Renton Rd", city="Ewa Beach", state="HI", zipcode="96706", description="Asing Community Park", clean=False, latitude=21.3508, 8 | longitude=-158.0253, created_at="2021-03-01 10:15:04", user_id="2") 9 | area_3 = Area(address="2199 Kalia Rd", city="Honolulu", state="HI", zipcode="96815", description="The stretch of Kalia road by Lewers Lounge", clean=False, latitude=21.2779, 10 | longitude=-157.8323, created_at="2021-02-26 12:00:00", user_id="4") 11 | area_4 = Area(address="Lower Saki Mana Rd", city="Waimea", state="HI", zipcode="96796", description="Polihale State Park", clean=False, latitude=22.0795, 12 | longitude=-159.7648, created_at="2021-04-01 07:03:12", user_id="1") 13 | area_5 = Area(address="200 W. Kawili St", city="Hilo", state="HI", zipcode="96720", description="Campus of University of Hawaii at Hilo", clean=False, latitude=19.7018462, 14 | longitude=-155.0791607, created_at="2021-04-01 07:27:44", user_id="3") 15 | area_6 = Area(address="Kahekili Hwy", city="Wailuku", state="HI", zipcode="96793", description="Waihee Ridge Trail", clean=False, latitude=20.9529, 16 | longitude=-156.5316, created_at="2021-04-12 13:45:29", user_id="4") 17 | area_7 = Area(address="6600 Makena Alanui", city="Kihei", state="HI", zipcode="96753", description="Makena Beach", clean=False, latitude=20.6316, 18 | longitude=-156.4448, created_at="2021-04-25 10:31:00", user_id="1") 19 | area_8 = Area(address="Kaahumanu Pl", city="Kailua-Kona", state="HI", zipcode="96740", description="Kailua Pier", clean=False, latitude=19.6400, 20 | longitude=-155.9969, created_at="2021-04-25 14:12:01", user_id="2") 21 | area_9 = Area(address="Mauna Kea Summit Rd", city="Hilo", state="HI", zipcode="96720", description="Lake Waiau Parking Lot", clean=False, latitude=19.810409, 22 | longitude=-155.46768, created_at="2021-04-27 08:28:55", user_id="5") 23 | 24 | db.session.add(area_1) 25 | db.session.add(area_2) 26 | db.session.add(area_3) 27 | db.session.add(area_4) 28 | db.session.add(area_5) 29 | db.session.add(area_6) 30 | db.session.add(area_7) 31 | db.session.add(area_8) 32 | db.session.add(area_9) 33 | 34 | db.session.commit() 35 | 36 | def undo_areas(): 37 | Area.query.delete() 38 | db.session.commit() -------------------------------------------------------------------------------- /app/seeds/events.py: -------------------------------------------------------------------------------- 1 | from app.models import db, Event 2 | 3 | def seed_events(): 4 | 5 | event_1 = Event(title="Ka Makana Swim Club", date_time="2021-04-03 08:00:00", description="The Ka Makana Swim Club is getting together to clean up the neighboring beach the first weekend in April", 6 | area_id=1, user_id=2) 7 | 8 | event_2 = Event(title="Family of Five", date_time="2021-04-04 12:00:00", description="My family will be spending the first Sunday in April picking up litter on the beach. Come join us!", 9 | area_id=1, user_id=1) 10 | 11 | event_3 = Event(title="Ewa Makai Middle School", date_time="2021-04-07 09:00:00", description="Our Ewa Makai eighth graders are spending a day in April cleaning up White Plains Beach.", 12 | area_id=1, user_id=5) 13 | 14 | event_4 = Event(title="Fanciscan Vistas Apartments", date_time="2021-04-12 07:15:00", description="Some of us at Franciscan Vistas Ewa Apartments are going to meet up before work and pick up trash at Asing Park. Always need help!", 15 | area_id=2, user_id=3) 16 | 17 | event_5 = Event(title="Service Hours", date_time="2021-04-15 12:00:00", description="I need to get more service hours for school so I'll be picking up trash at the park over lunch.", 18 | area_id=2, user_id=1) 19 | 20 | event_6 = Event(title="Lewers Lounge Staff", date_time="2021-04-27 08:30:00", description="The staff at Lewers Lounge is going to be taking the morning off to clean up our surrounding area. If anyone is interested don't hesitate to join us!", 21 | area_id=3, user_id=1) 22 | 23 | event_7 = Event(title="808 Cleanups", date_time="2021-05-01 09:00:00", description="808 Cleanups is going to be focusing on the Waikiki area for the month and we are going to start with the Int. Market Place area.", 24 | area_id=3, user_id=3) 25 | 26 | event_8 = Event(title="Neighborhood Cleanup", date_time="2021-04-07 08:30:00", description="Our neighborhood is going to devote time to picking up Polihale State Park. We all visit the beach there regularly and take ownership in keeping it clean.", 27 | area_id=4, user_id=4) 28 | 29 | event_9 = Event(title="Parks & Rec", date_time="2021-04-10 10:00:00", description="Kauai Parks & Recreation will be sending a team over to Polihale State Park to remove whatever trash and litter remains. We'd love the help and support of the community!", 30 | area_id=4, user_id=1) 31 | 32 | event_10 = Event(title="Hale 'Alahonua", date_time="2021-04-15 17:00:00", description="Our dorm is going to spend the afternoon picking up trash throughout campus. Anyone is welcome!", 33 | area_id=5, user_id=4) 34 | 35 | event_11 = Event(title="'92 Alumni", date_time="2021-04-20 12:20:00", description="The UH Hilo class of '92 is meeting on campus to take part in litter clean up. Lets go Vulcans!", 36 | area_id=5, user_id=1) 37 | 38 | event_12 = Event(title="Family Clean Up", date_time="2021-04-17 10:00:00", description="Our entire family is coming together to clean up trash along Waihee Ridge Trail. We all frequent the trail and noticed the litter accumulating.", 39 | area_id=6, user_id=1) 40 | 41 | event_13 = Event(title="Just Me", date_time="2021-04-20 15:30:00", description="I walk Waihee Ridge Trail every weekend and I can't stand to see it get covered with litter. I'll be going out to pick up what I can and anyone who wants to join me is welcome.", 42 | area_id=6, user_id=5) 43 | 44 | event_14 = Event(title="Makena Golf & Beach Club", date_time="2021-05-05 13:15:00", description="We noticed a decent amount of trash washed up on shore of Makena Beach and our staff and members are going to spend the afternoon cleaning it up. Come join!", 45 | area_id=7, user_id=2) 46 | 47 | event_15 = Event(title="Marine Ecology", date_time="2021-05-02 12:00:00", description="Our marine ecology class is taking a field trip to the pier to pick up trash in the area and keep it out of the ocean.", 48 | area_id=8, user_id=4) 49 | 50 | event_16 = Event(title="O'okala Fellowship", date_time="2021-04-28 08:00:00", description="Our church members are going to take the initiative to clean up the mess that was left at the Lake Waiau Parking Lot.", 51 | area_id=9, user_id=2) 52 | 53 | 54 | db.session.add(event_1) 55 | db.session.add(event_2) 56 | db.session.add(event_3) 57 | db.session.add(event_4) 58 | db.session.add(event_5) 59 | db.session.add(event_6) 60 | db.session.add(event_7) 61 | db.session.add(event_8) 62 | db.session.add(event_9) 63 | db.session.add(event_10) 64 | db.session.add(event_11) 65 | db.session.add(event_12) 66 | db.session.add(event_13) 67 | db.session.add(event_14) 68 | db.session.add(event_15) 69 | db.session.add(event_16) 70 | 71 | db.session.commit() 72 | 73 | def undo_events(): 74 | Event.query.delete() 75 | db.session.commit() -------------------------------------------------------------------------------- /app/seeds/items.py: -------------------------------------------------------------------------------- 1 | from app.models import db, Item 2 | 3 | def seed_items(): 4 | #Bathroom 5 | item_1 = Item(name="Toothpaste", description="Instead of buying a tube of toothpaste every month or so you can shop for plastic free toothpaste as a better alternative. Bite is the brand I use and it gets the job done!", 6 | user_id="4", type_id="3") 7 | item_2 = Item(name="Soap", description="I recently switched back to using soap bars instead of the liquid soap that comes in plastic bottles. Schmidt's is the brand that I currently use and it smells great!", 8 | user_id="1", type_id="3") 9 | item_3 = Item(name="Face pads", description="I finally switched from my single use face pads I use to wash my face and purchased washable face pads that feel great and work just as well! Seek Bamboo is the brand I use.", 10 | user_id="2", type_id="3") 11 | item_4 = Item(name="Q-Tips", description="If you haven't heard...toss the q-tips! They only push the wax further in to your ear and are a needless use of plastic.", 12 | user_id="3", type_id="3") 13 | #Kitchen 14 | item_5 = Item(name="Food Covers", description="I stopped buying ziplock bags because I only use stretchy silicone food lids. They come in many sizes and in square and circlular shapes. Just stretch over your casserole dish or salad bowl and you're set.", 15 | user_id="5", type_id="1") 16 | item_6 = Item(name="Multi-Surface Cleaner", description="I bought the multi-surface starter kit from Blueland and they sent me a reusable spray bottle that I fill on my own and drop in one of their provided tablets.", 17 | user_id="4", type_id="1") 18 | item_7 = Item(name="Reusable Bags", description="Buy reusable grocery bags as well as reusable produce bags for when you go to the grocery store. This may seem simple but its a great place to start!", 19 | user_id="1", type_id="1") 20 | item_8 = Item(name="Milk", description="Oat and/or Soy milk uses significantly less land and water to produce than dairy milk.", 21 | user_id="3", type_id="1") 22 | #Office 23 | item_9 = Item(name="Highlighters", description="You can now find eco friendly wooden highlighters pretty much anywhere online.", 24 | user_id="1", type_id="2") 25 | item_10 = Item(name="Electronic Recycling", description="Electronic stores like Best Buy will actually take your old electronics and recycle them. They typically have drop off boxes at the store front.", 26 | user_id="2", type_id="2") 27 | item_11 = Item(name="Go Digital", description="I noticed a huge decrease in my paper usage after I made a conscious effort to switch to digital files. Google Drive is what I use but there are other options.", 28 | user_id="1", type_id="2") 29 | item_12 = Item(name="Pens", description="You can bring your old pens, highlighters, and mechanical pencils to most office supply stores to recycle them. I take mine to Staples.", 30 | user_id="4", type_id="2") 31 | #Closet 32 | item_13 = Item(name="Old Clothes", description="Some retail stores will offer you credit if you send them old clothes. Marine Layer will give you $5 credit per tee that you send in (up to $25). Of course you can send them more than 5 tee's if you want.", 33 | user_id="3", type_id="4") 34 | item_14 = Item(name="Jeans", description="Save water and stop washing your jeans. You'd be surprised how long you can go before you actually need to wash your jeans. If you're just wearing them occassionally you could go up to 6 months or more before needing them washed.", 35 | user_id="2", type_id="4") 36 | item_15 = Item(name="Micro Plastics", description="Fabrics that release microplastics: Polyester, Nylon, Acrylic, Fleece, Spandex, Acetate. Fabrics that dont: Cotton, Flax, Hemp, Jute, Linen, Ramie, Sisal, Kenaf.", 37 | user_id="1", type_id="4") 38 | item_16 = Item(name="Hangers", description="Recycled fiber board hangers are a nice substitute for plastic hangers, but before you buy them, ask yourself if all the clothes currently on hangers actually need hangers. Ditto Hangers are the brand I use.", 39 | user_id="5", type_id="4") 40 | #Laundry 41 | item_17 = Item(name="Soap Nuts", description="Instead of buying laundry detergent in plastic containers you can use soap nuts (from Soapberry trees) instead. You can reuse soap nuts up to 6 loads of laundry and they are compostable.", 42 | user_id="1", type_id="5") 43 | item_18 = Item(name="Lemons", description="I recently discovered that you can avoid using bleach by soaking stained clothes overnight in one gallon of hot water and a half cup of lemon juice. It gets the stains out, and you can avoid the harsh chemicals.", 44 | user_id="2", type_id="5") 45 | item_19 = Item(name="Dryer Balls", description="Dryer balls retain heat, help clothes stay separated, and reduce drying time by 25%. Wool dryer balls, which are easy to find, can last up to 1,000 loads.", 46 | user_id="3", type_id="5") 47 | item_20 = Item(name="Hang Dry", description="If you dont need youre clothes dry right away, consider hanging them out to dry. Your dryer is more than likely one of the top energy-hungry appliances in your house...and your clothes will last longer!", 48 | user_id="4", type_id="5") 49 | #Other 50 | item_21 = Item(name="Broom", description="When your broom begings to fray and it doesnt sweep like it used to, you can extend its life by simply trimming the frayed ends with a pair of scissors.", 51 | user_id="5", type_id="6") 52 | item_22 = Item(name="Composting", description="Divert waste from the landfill and start composting items like food scraps, coffee grinds, and grass clippings.", 53 | user_id="1", type_id="6") 54 | item_23 = Item(name="Bicycle", description="If can swing it, start commuting on your bike. You'd be surprised the effect it can have on emmissions and fuel consumption if we all rode our bikes. Not to mention you'll be in better shape and save on gas!", 55 | user_id="2", type_id="6") 56 | item_24 = Item(name="Paperless Billing", description="This one might seem simple, but save some trees and enroll in paperless billing.", 57 | user_id="3", type_id="6") 58 | 59 | db.session.add(item_1) 60 | db.session.add(item_2) 61 | db.session.add(item_3) 62 | db.session.add(item_4) 63 | db.session.add(item_5) 64 | db.session.add(item_6) 65 | db.session.add(item_7) 66 | db.session.add(item_8) 67 | db.session.add(item_9) 68 | db.session.add(item_10) 69 | db.session.add(item_11) 70 | db.session.add(item_12) 71 | db.session.add(item_13) 72 | db.session.add(item_14) 73 | db.session.add(item_15) 74 | db.session.add(item_16) 75 | db.session.add(item_17) 76 | db.session.add(item_18) 77 | db.session.add(item_19) 78 | db.session.add(item_20) 79 | db.session.add(item_21) 80 | db.session.add(item_22) 81 | db.session.add(item_23) 82 | db.session.add(item_24) 83 | 84 | db.session.commit() 85 | 86 | def undo_items(): 87 | Item.query.delete() 88 | db.session.commit() 89 | -------------------------------------------------------------------------------- /app/seeds/types.py: -------------------------------------------------------------------------------- 1 | from app.models import db, Type 2 | 3 | def seed_types(): 4 | 5 | type_1 = Type(name="Kitchen") 6 | type_2 = Type(name="Office") 7 | type_3 = Type(name="Bathroom") 8 | type_4 = Type(name="Closet") 9 | type_5 = Type(name="Laundry") 10 | type_6 = Type(name="Miscellaneous") 11 | 12 | db.session.add(type_1) 13 | db.session.add(type_2) 14 | db.session.add(type_3) 15 | db.session.add(type_4) 16 | db.session.add(type_5) 17 | db.session.add(type_6) 18 | 19 | db.session.commit() 20 | 21 | def undo_types(): 22 | Type.query.delete() 23 | db.session.commit() 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/seeds/users.py: -------------------------------------------------------------------------------- 1 | from werkzeug.security import generate_password_hash 2 | from app.models import db, User 3 | 4 | # Adds a demo user, you can add other users here if you want 5 | def seed_users(): 6 | 7 | demo = User(first_name="Demo", last_name="User", 8 | email='demo@aa.io', password='password') 9 | 10 | quintin = User(first_name="Quintin", last_name="Hull", 11 | email="quintinhull92@gmail.com", password='password') 12 | 13 | olivia = User(first_name="Olivia", last_name="Jones", 14 | email="olivia21@gmail.com", password='passOJ21') 15 | 16 | tito = User(first_name="Tito", last_name="Makani", 17 | email="tito@shoreshack.com", password="alohatothat") 18 | 19 | reggie = User(first_name="Reggie", last_name="Smoove", 20 | email="smoovereg@gmail.com", password="smoove24") 21 | 22 | db.session.add(demo) 23 | db.session.add(quintin) 24 | db.session.add(olivia) 25 | db.session.add(tito) 26 | db.session.add(reggie) 27 | 28 | db.session.commit() 29 | 30 | # Uses a raw SQL query to TRUNCATE the users table. 31 | # SQLAlchemy doesn't have a built in function to do this 32 | # TRUNCATE Removes all the data from the table, and resets 33 | # the auto incrementing primary key 34 | def undo_users(): 35 | db.session.execute('TRUNCATE users;') 36 | db.session.commit() 37 | -------------------------------------------------------------------------------- /dev-requirements.txt: -------------------------------------------------------------------------------- 1 | psycopg2-binary==2.8.6 2 | -------------------------------------------------------------------------------- /migrations/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. -------------------------------------------------------------------------------- /migrations/alembic.ini: -------------------------------------------------------------------------------- 1 | # A generic, single database configuration. 2 | 3 | [alembic] 4 | # template used to generate migration files 5 | file_template = %%(year)d%%(month).2d%%(day).2d_%%(hour).2d%%(minute).2d%%(second).2d_%%(slug)s 6 | 7 | # set to 'true' to run the environment during 8 | # the 'revision' command, regardless of autogenerate 9 | # revision_environment = false 10 | 11 | 12 | # Logging configuration 13 | [loggers] 14 | keys = root,sqlalchemy,alembic 15 | 16 | [handlers] 17 | keys = console 18 | 19 | [formatters] 20 | keys = generic 21 | 22 | [logger_root] 23 | level = WARN 24 | handlers = console 25 | qualname = 26 | 27 | [logger_sqlalchemy] 28 | level = WARN 29 | handlers = 30 | qualname = sqlalchemy.engine 31 | 32 | [logger_alembic] 33 | level = INFO 34 | handlers = 35 | qualname = alembic 36 | 37 | [handler_console] 38 | class = StreamHandler 39 | args = (sys.stderr,) 40 | level = NOTSET 41 | formatter = generic 42 | 43 | [formatter_generic] 44 | format = %(levelname)-5.5s [%(name)s] %(message)s 45 | datefmt = %H:%M:%S 46 | -------------------------------------------------------------------------------- /migrations/env.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | 3 | import logging 4 | from logging.config import fileConfig 5 | 6 | from sqlalchemy import engine_from_config 7 | from sqlalchemy import pool 8 | 9 | from alembic import context 10 | 11 | # this is the Alembic Config object, which provides 12 | # access to the values within the .ini file in use. 13 | config = context.config 14 | 15 | # Interpret the config file for Python logging. 16 | # This line sets up loggers basically. 17 | fileConfig(config.config_file_name) 18 | logger = logging.getLogger('alembic.env') 19 | 20 | # add your model's MetaData object here 21 | # for 'autogenerate' support 22 | # from myapp import mymodel 23 | # target_metadata = mymodel.Base.metadata 24 | from flask import current_app 25 | config.set_main_option( 26 | 'sqlalchemy.url', 27 | str(current_app.extensions['migrate'].db.engine.url).replace('%', '%%')) 28 | target_metadata = current_app.extensions['migrate'].db.metadata 29 | 30 | # other values from the config, defined by the needs of env.py, 31 | # can be acquired: 32 | # my_important_option = config.get_main_option("my_important_option") 33 | # ... etc. 34 | 35 | 36 | def run_migrations_offline(): 37 | """Run migrations in 'offline' mode. 38 | 39 | This configures the context with just a URL 40 | and not an Engine, though an Engine is acceptable 41 | here as well. By skipping the Engine creation 42 | we don't even need a DBAPI to be available. 43 | 44 | Calls to context.execute() here emit the given string to the 45 | script output. 46 | 47 | """ 48 | url = config.get_main_option("sqlalchemy.url") 49 | context.configure( 50 | url=url, target_metadata=target_metadata, literal_binds=True 51 | ) 52 | 53 | with context.begin_transaction(): 54 | context.run_migrations() 55 | 56 | 57 | def run_migrations_online(): 58 | """Run migrations in 'online' mode. 59 | 60 | In this scenario we need to create an Engine 61 | and associate a connection with the context. 62 | 63 | """ 64 | 65 | # this callback is used to prevent an auto-migration from being generated 66 | # when there are no changes to the schema 67 | # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html 68 | def process_revision_directives(context, revision, directives): 69 | if getattr(config.cmd_opts, 'autogenerate', False): 70 | script = directives[0] 71 | if script.upgrade_ops.is_empty(): 72 | directives[:] = [] 73 | logger.info('No changes in schema detected.') 74 | 75 | connectable = engine_from_config( 76 | config.get_section(config.config_ini_section), 77 | prefix='sqlalchemy.', 78 | poolclass=pool.NullPool, 79 | ) 80 | 81 | with connectable.connect() as connection: 82 | context.configure( 83 | connection=connection, 84 | target_metadata=target_metadata, 85 | process_revision_directives=process_revision_directives, 86 | **current_app.extensions['migrate'].configure_args 87 | ) 88 | 89 | with context.begin_transaction(): 90 | context.run_migrations() 91 | 92 | 93 | if context.is_offline_mode(): 94 | run_migrations_offline() 95 | else: 96 | run_migrations_online() 97 | -------------------------------------------------------------------------------- /migrations/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /migrations/versions/20201120_150602_create_users_table.py: -------------------------------------------------------------------------------- 1 | """create_users_table 2 | 3 | Revision ID: ffdc0a98111c 4 | Revises: 5 | Create Date: 2020-11-20 15:06:02.230689 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'ffdc0a98111c' 14 | down_revision = None 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_table('users', 22 | sa.Column('id', sa.Integer(), nullable=False), 23 | sa.Column('first_name', sa.String(length=50), nullable=False), 24 | sa.Column('last_name', sa.String(length=50), nullable=False), 25 | sa.Column('email', sa.String(length=255), nullable=False), 26 | sa.Column('hashed_password', sa.String(length=255), nullable=False), 27 | sa.PrimaryKeyConstraint('id'), 28 | sa.UniqueConstraint('email'), 29 | ) 30 | # ### end Alembic commands ###qqqqqqqqq 31 | 32 | 33 | def downgrade(): 34 | # ### commands auto generated by Alembic - please adjust! ### 35 | op.drop_table('users') 36 | # ### end Alembic commands ### 37 | -------------------------------------------------------------------------------- /migrations/versions/20210305_134652_.py: -------------------------------------------------------------------------------- 1 | """empty message 2 | 3 | Revision ID: 42421609039e 4 | Revises: ffdc0a98111c 5 | Create Date: 2021-03-05 13:46:52.382327 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '42421609039e' 14 | down_revision = 'ffdc0a98111c' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_table('types', 22 | sa.Column('id', sa.Integer(), nullable=False), 23 | sa.Column('name', sa.String(length=50), nullable=False), 24 | sa.PrimaryKeyConstraint('id') 25 | ) 26 | op.create_table('areas', 27 | sa.Column('id', sa.Integer(), nullable=False), 28 | sa.Column('address', sa.String(length=50), nullable=False), 29 | sa.Column('city', sa.String(length=50), nullable=False), 30 | sa.Column('state', sa.String(length=2), nullable=False), 31 | sa.Column('zipcode', sa.Integer(), nullable=False), 32 | sa.Column('description', sa.String(length=250), nullable=False), 33 | sa.Column('clean', sa.Boolean(), nullable=False), 34 | sa.Column('latitude', sa.Float(), nullable=True), 35 | sa.Column('longitude', sa.Float(), nullable=True), 36 | sa.Column('created_at', sa.DateTime(), nullable=False), 37 | sa.Column('user_id', sa.Integer(), nullable=False), 38 | sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), 39 | sa.PrimaryKeyConstraint('id') 40 | ) 41 | op.create_table('items', 42 | sa.Column('id', sa.Integer(), nullable=False), 43 | sa.Column('name', sa.String(length=50), nullable=False), 44 | sa.Column('description', sa.String(length=250), nullable=False), 45 | sa.Column('user_id', sa.Integer(), nullable=False), 46 | sa.Column('type_id', sa.Integer(), nullable=False), 47 | sa.ForeignKeyConstraint(['type_id'], ['types.id'], ), 48 | sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), 49 | sa.PrimaryKeyConstraint('id') 50 | ) 51 | op.create_table('events', 52 | sa.Column('id', sa.Integer(), nullable=False), 53 | sa.Column('title', sa.String(length=50), nullable=False), 54 | sa.Column('date_time', sa.DateTime(), nullable=False), 55 | sa.Column('description', sa.String(length=250), nullable=False), 56 | sa.Column('area_id', sa.Integer(), nullable=False), 57 | sa.Column('user_id', sa.Integer(), nullable=False), 58 | sa.ForeignKeyConstraint(['area_id'], ['areas.id'], ), 59 | sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), 60 | sa.PrimaryKeyConstraint('id') 61 | ) 62 | # ### end Alembic commands ### 63 | 64 | 65 | def downgrade(): 66 | # ### commands auto generated by Alembic - please adjust! ### 67 | op.drop_table('events') 68 | op.drop_table('items') 69 | op.drop_table('areas') 70 | op.drop_table('types') 71 | # ### end Alembic commands ### 72 | -------------------------------------------------------------------------------- /react-app/.env.example: -------------------------------------------------------------------------------- 1 | REACT_APP_BASE_URL=http://localhost:5000 2 | REACT_APP_GOOGLE_KEY= 3 | -------------------------------------------------------------------------------- /react-app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /react-app/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | Your React App will live here. While is development, run this application from this location using `npm start`. 4 | 5 | 6 | No environment variables are needed to run this application in development, but be sure to set the REACT_APP_BASE_URL environment variable in heroku! 7 | 8 | This app will be automatically built when you deploy to heroku, please see the `heroku-postbuild` script in your `express.js` applications `package.json` to see how this works. 9 | -------------------------------------------------------------------------------- /react-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^4.2.4", 7 | "@testing-library/react": "^9.3.2", 8 | "@testing-library/user-event": "^7.1.2", 9 | "date-fns": "^2.19.0", 10 | "dotenv": "^8.2.0", 11 | "http-proxy-middleware": "^1.0.5", 12 | "query-string": "^6.14.1", 13 | "react": "^17.0.0", 14 | "react-dom": "^17.0.0", 15 | "react-geocode": "^0.2.3", 16 | "react-google-maps": "^9.4.5", 17 | "react-nice-dates": "^3.0.7", 18 | "react-redux": "^7.2.2", 19 | "react-router-dom": "^5.2.0", 20 | "react-scripts": "3.4.3", 21 | "redux": "^4.0.5", 22 | "redux-logger": "^3.0.6", 23 | "redux-thunk": "^2.3.0" 24 | }, 25 | "scripts": { 26 | "start": "react-scripts start", 27 | "build": "react-scripts build", 28 | "test": "react-scripts test", 29 | "eject": "react-scripts eject" 30 | }, 31 | "eslintConfig": { 32 | "extends": "react-app" 33 | }, 34 | "browserslist": { 35 | "production": [ 36 | ">0.2%", 37 | "not dead", 38 | "not op_mini all" 39 | ], 40 | "development": [ 41 | "last 1 chrome version", 42 | "last 1 firefox version", 43 | "last 1 safari version" 44 | ] 45 | }, 46 | "proxy": "http://localhost:5000" 47 | } 48 | -------------------------------------------------------------------------------- /react-app/public/beachLitter.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuintinHull/trashed/bde412e1f2da17e53f99ba47f6b4d68e9c663ea8/react-app/public/beachLitter.jpg -------------------------------------------------------------------------------- /react-app/public/carousel_4.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /react-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuintinHull/trashed/bde412e1f2da17e53f99ba47f6b4d68e9c663ea8/react-app/public/favicon.ico -------------------------------------------------------------------------------- /react-app/public/gitHubMark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuintinHull/trashed/bde412e1f2da17e53f99ba47f6b4d68e9c663ea8/react-app/public/gitHubMark.png -------------------------------------------------------------------------------- /react-app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | TRASHED 11 | 12 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /react-app/public/linkedIn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuintinHull/trashed/bde412e1f2da17e53f99ba47f6b4d68e9c663ea8/react-app/public/linkedIn.png -------------------------------------------------------------------------------- /react-app/public/logoWithName.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuintinHull/trashed/bde412e1f2da17e53f99ba47f6b4d68e9c663ea8/react-app/public/logoWithName.png -------------------------------------------------------------------------------- /react-app/public/simpleLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuintinHull/trashed/bde412e1f2da17e53f99ba47f6b4d68e9c663ea8/react-app/public/simpleLogo.png -------------------------------------------------------------------------------- /react-app/public/trashBin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuintinHull/trashed/bde412e1f2da17e53f99ba47f6b4d68e9c663ea8/react-app/public/trashBin.jpg -------------------------------------------------------------------------------- /react-app/public/trashed_home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuintinHull/trashed/bde412e1f2da17e53f99ba47f6b4d68e9c663ea8/react-app/public/trashed_home.png -------------------------------------------------------------------------------- /react-app/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { BrowserRouter, Route, Switch } from "react-router-dom"; 3 | import { Provider as ReduxProvider } from "react-redux"; 4 | import LoginForm from "./components/auth/LoginForm"; 5 | import SignUpForm from "./components/auth/SignUpForm"; 6 | import NavBar from "./components/NavBar"; 7 | import ProtectedRoute from "./components/auth/ProtectedRoute"; 8 | import UsersList from "./components/UsersList"; 9 | import User from "./components/User"; 10 | import { authenticate } from "./services/auth"; 11 | import configureStore from "./store"; 12 | 13 | import HomePage from "./components/HomePage"; 14 | import AreaView from "./components/AreaView"; 15 | import EventView from "./components/EventView"; 16 | import SearchResult from "./components/SearchResult"; 17 | import ItemView from "./components/ItemView"; 18 | import TypeView from "./components/TypeView"; 19 | import SingleItemView from "./components/SingleItemView"; 20 | import Footer from "./components/Footer"; 21 | import About from "./components/About"; 22 | import { ModalProvider } from "./context/Modal" 23 | 24 | const store = configureStore(); 25 | 26 | function App() { 27 | const [authenticated, setAuthenticated] = useState(false); 28 | const [loaded, setLoaded] = useState(false); 29 | const [showLogin, setShowLogin] = useState(false); 30 | const [showSignUp, setShowSignUp] = useState(false); 31 | 32 | useEffect(() => { 33 | (async () => { 34 | const user = await authenticate(); 35 | if (!user.errors) { 36 | setAuthenticated(true); 37 | } 38 | setLoaded(true); 39 | })(); 40 | }, []); 41 | 42 | if (!loaded) { 43 | return null; 44 | } 45 | 46 | return ( 47 | 48 | 49 | 50 | 54 | 55 | 56 | 60 | 61 | 62 | 63 | 64 | 65 | 69 | 70 | 75 | 76 | 77 | 82 | 83 | 84 | 85 | 91 | 92 | 98 | 99 | 100 | 106 | 107 | 108 | 113 | 114 | 115 | 121 | 122 | 123 | 129 | 130 | 131 | 137 | 138 | 139 | 140 |