├── README.md ├── application.py ├── data.db ├── static ├── css │ └── custom.css ├── img │ ├── argentina.png │ ├── belgium.png │ ├── bj.jpg │ ├── brasil.jpg │ ├── colombia.jpg │ ├── cor.png │ ├── costarica.jpg │ ├── croacia.jpg │ ├── egypt.jpg │ ├── england.jpg │ ├── fcb.jpg │ ├── fla.png │ ├── france.jpg │ ├── germany.png │ ├── iceland.jpg │ ├── juv.jpg │ ├── mc.jpg │ ├── mexico.jpg │ ├── mil.png │ ├── mu.png │ ├── peru.jpg │ ├── rm.png │ ├── rp.png │ ├── sale-icon.png │ ├── senegal.jpg │ ├── spain.jpg │ ├── sweden.jpg │ └── uruguay.png └── js │ ├── myscripts.js │ └── validate.js └── templates ├── base.html ├── cart.html ├── history.html ├── index.html ├── login.html └── new.html /README.md: -------------------------------------------------------------------------------- 1 | # flask-ecomm 2 | An eCommerce App built with Flask, Jinja, SQLite, jQuery and Bootstrap 3 | 4 | This app was my CS50x final project. 5 | 6 | The app loads a gallery of soccer shirts that includes: image, description, price, and a small form to add item to cart. The shirt info is stored in a SQLite database and is displayed using Bootstrap's card class. 7 | 8 | 9 | 10 | The app includes a series of filters implemented using SQLite queries so you can see only shirts that match a certain filter: shirts by region, clubs vs. national teams, shirts on sale, etc. 11 | 12 | 13 | 14 | If a user is not logged in and tries to add something to the shopping cart she will see a warning message (implemented with jQuery) asking her to log in. 15 | 16 | Once registered and logged in, the user can add shirts to the shopping cart. A link to the shopping cart can be found at the top right of the screen, showing the amount of items in the cart as well as the sub-total in dollars. Clicking the shopping cart link opens a Bootstrap modal window showing the shopping cart in more detail. 17 | 18 | 19 | 20 | If you want to make changes, like add one more shirt or remove a shirt, you can click on the Make Changes button and you will be taken to the full version of the shopping cart. 21 | 22 | 23 | 24 | Once you check out, the idea is to be sent to a payment processor. However, that part is not implemented yet. 25 | 26 | If you want to see your purchase history, just click on the You Bought link and you will see all the shirts you have ever bought. You will also find a Buy Again link that will direct you to the product page in case you want to buy it again. 27 | 28 | 29 | 30 | Once you're finished, you can just log out. 31 | 32 | If you want to see the app in action, fork the repository to your own computer and perform the following commands from the command line in your project folder: 33 | 34 | export FLASK_APP=application.py
35 | flask run 36 | 37 | This assumes you have Python, Flask and SQLite installed in your computer, as well as a link to Bootstrap and the following modules necessary to run application.py installed: 38 | 39 | 40 | from cs50 import SQL
41 | from flask_session import Session
42 | from flask import Flask, render_template, redirect, request, session, jsonify
43 | from datetime import datetime 44 | 45 | -------------------------------------------------------------------------------- /application.py: -------------------------------------------------------------------------------- 1 | from cs50 import SQL 2 | from flask_session import Session 3 | from flask import Flask, render_template, redirect, request, session, jsonify 4 | from datetime import datetime 5 | 6 | # # Instantiate Flask object named app 7 | app = Flask(__name__) 8 | 9 | # # Configure sessions 10 | app.config["SESSION_PERMANENT"] = False 11 | app.config["SESSION_TYPE"] = "filesystem" 12 | Session(app) 13 | 14 | # Creates a connection to the database 15 | db = SQL ( "sqlite:///data.db" ) 16 | 17 | @app.route("/") 18 | def index(): 19 | shirts = db.execute("SELECT * FROM shirts ORDER BY team ASC") 20 | shirtsLen = len(shirts) 21 | # Initialize variables 22 | shoppingCart = [] 23 | shopLen = len(shoppingCart) 24 | totItems, total, display = 0, 0, 0 25 | if 'user' in session: 26 | shoppingCart = db.execute("SELECT team, image, SUM(qty), SUM(subTotal), price, id FROM cart GROUP BY team") 27 | shopLen = len(shoppingCart) 28 | for i in range(shopLen): 29 | total += shoppingCart[i]["SUM(subTotal)"] 30 | totItems += shoppingCart[i]["SUM(qty)"] 31 | shirts = db.execute("SELECT * FROM shirts ORDER BY team ASC") 32 | shirtsLen = len(shirts) 33 | return render_template ("index.html", shoppingCart=shoppingCart, shirts=shirts, shopLen=shopLen, shirtsLen=shirtsLen, total=total, totItems=totItems, display=display, session=session ) 34 | return render_template ( "index.html", shirts=shirts, shoppingCart=shoppingCart, shirtsLen=shirtsLen, shopLen=shopLen, total=total, totItems=totItems, display=display) 35 | 36 | 37 | @app.route("/buy/") 38 | def buy(): 39 | # Initialize shopping cart variables 40 | shoppingCart = [] 41 | shopLen = len(shoppingCart) 42 | totItems, total, display = 0, 0, 0 43 | qty = int(request.args.get('quantity')) 44 | if session: 45 | # Store id of the selected shirt 46 | id = int(request.args.get('id')) 47 | # Select info of selected shirt from database 48 | goods = db.execute("SELECT * FROM shirts WHERE id = :id", id=id) 49 | # Extract values from selected shirt record 50 | # Check if shirt is on sale to determine price 51 | if(goods[0]["onSale"] == 1): 52 | price = goods[0]["onSalePrice"] 53 | else: 54 | price = goods[0]["price"] 55 | team = goods[0]["team"] 56 | image = goods[0]["image"] 57 | subTotal = qty * price 58 | # Insert selected shirt into shopping cart 59 | db.execute("INSERT INTO cart (id, qty, team, image, price, subTotal) VALUES (:id, :qty, :team, :image, :price, :subTotal)", id=id, qty=qty, team=team, image=image, price=price, subTotal=subTotal) 60 | shoppingCart = db.execute("SELECT team, image, SUM(qty), SUM(subTotal), price, id FROM cart GROUP BY team") 61 | shopLen = len(shoppingCart) 62 | # Rebuild shopping cart 63 | for i in range(shopLen): 64 | total += shoppingCart[i]["SUM(subTotal)"] 65 | totItems += shoppingCart[i]["SUM(qty)"] 66 | # Select all shirts for home page view 67 | shirts = db.execute("SELECT * FROM shirts ORDER BY team ASC") 68 | shirtsLen = len(shirts) 69 | # Go back to home page 70 | return render_template ("index.html", shoppingCart=shoppingCart, shirts=shirts, shopLen=shopLen, shirtsLen=shirtsLen, total=total, totItems=totItems, display=display, session=session ) 71 | 72 | 73 | @app.route("/update/") 74 | def update(): 75 | # Initialize shopping cart variables 76 | shoppingCart = [] 77 | shopLen = len(shoppingCart) 78 | totItems, total, display = 0, 0, 0 79 | qty = int(request.args.get('quantity')) 80 | if session: 81 | # Store id of the selected shirt 82 | id = int(request.args.get('id')) 83 | db.execute("DELETE FROM cart WHERE id = :id", id=id) 84 | # Select info of selected shirt from database 85 | goods = db.execute("SELECT * FROM shirts WHERE id = :id", id=id) 86 | # Extract values from selected shirt record 87 | # Check if shirt is on sale to determine price 88 | if(goods[0]["onSale"] == 1): 89 | price = goods[0]["onSalePrice"] 90 | else: 91 | price = goods[0]["price"] 92 | team = goods[0]["team"] 93 | image = goods[0]["image"] 94 | subTotal = qty * price 95 | # Insert selected shirt into shopping cart 96 | db.execute("INSERT INTO cart (id, qty, team, image, price, subTotal) VALUES (:id, :qty, :team, :image, :price, :subTotal)", id=id, qty=qty, team=team, image=image, price=price, subTotal=subTotal) 97 | shoppingCart = db.execute("SELECT team, image, SUM(qty), SUM(subTotal), price, id FROM cart GROUP BY team") 98 | shopLen = len(shoppingCart) 99 | # Rebuild shopping cart 100 | for i in range(shopLen): 101 | total += shoppingCart[i]["SUM(subTotal)"] 102 | totItems += shoppingCart[i]["SUM(qty)"] 103 | # Go back to cart page 104 | return render_template ("cart.html", shoppingCart=shoppingCart, shopLen=shopLen, total=total, totItems=totItems, display=display, session=session ) 105 | 106 | 107 | @app.route("/filter/") 108 | def filter(): 109 | if request.args.get('continent'): 110 | query = request.args.get('continent') 111 | shirts = db.execute("SELECT * FROM shirts WHERE continent = :query ORDER BY team ASC", query=query ) 112 | if request.args.get('sale'): 113 | query = request.args.get('sale') 114 | shirts = db.execute("SELECT * FROM shirts WHERE onSale = :query ORDER BY team ASC", query=query) 115 | if request.args.get('id'): 116 | query = int(request.args.get('id')) 117 | shirts = db.execute("SELECT * FROM shirts WHERE id = :query ORDER BY team ASC", query=query) 118 | if request.args.get('kind'): 119 | query = request.args.get('kind') 120 | shirts = db.execute("SELECT * FROM shirts WHERE kind = :query ORDER BY team ASC", query=query) 121 | if request.args.get('price'): 122 | query = request.args.get('price') 123 | shirts = db.execute("SELECT * FROM shirts ORDER BY onSalePrice ASC") 124 | shirtsLen = len(shirts) 125 | # Initialize shopping cart variables 126 | shoppingCart = [] 127 | shopLen = len(shoppingCart) 128 | totItems, total, display = 0, 0, 0 129 | if 'user' in session: 130 | # Rebuild shopping cart 131 | shoppingCart = db.execute("SELECT team, image, SUM(qty), SUM(subTotal), price, id FROM cart GROUP BY team") 132 | shopLen = len(shoppingCart) 133 | for i in range(shopLen): 134 | total += shoppingCart[i]["SUM(subTotal)"] 135 | totItems += shoppingCart[i]["SUM(qty)"] 136 | # Render filtered view 137 | return render_template ("index.html", shoppingCart=shoppingCart, shirts=shirts, shopLen=shopLen, shirtsLen=shirtsLen, total=total, totItems=totItems, display=display, session=session ) 138 | # Render filtered view 139 | return render_template ( "index.html", shirts=shirts, shoppingCart=shoppingCart, shirtsLen=shirtsLen, shopLen=shopLen, total=total, totItems=totItems, display=display) 140 | 141 | 142 | @app.route("/checkout/") 143 | def checkout(): 144 | order = db.execute("SELECT * from cart") 145 | # Update purchase history of current customer 146 | for item in order: 147 | db.execute("INSERT INTO purchases (uid, id, team, image, quantity) VALUES(:uid, :id, :team, :image, :quantity)", uid=session["uid"], id=item["id"], team=item["team"], image=item["image"], quantity=item["qty"] ) 148 | # Clear shopping cart 149 | db.execute("DELETE from cart") 150 | shoppingCart = [] 151 | shopLen = len(shoppingCart) 152 | totItems, total, display = 0, 0, 0 153 | # Redirect to home page 154 | return redirect('/') 155 | 156 | 157 | @app.route("/remove/", methods=["GET"]) 158 | def remove(): 159 | # Get the id of shirt selected to be removed 160 | out = int(request.args.get("id")) 161 | # Remove shirt from shopping cart 162 | db.execute("DELETE from cart WHERE id=:id", id=out) 163 | # Initialize shopping cart variables 164 | totItems, total, display = 0, 0, 0 165 | # Rebuild shopping cart 166 | shoppingCart = db.execute("SELECT team, image, SUM(qty), SUM(subTotal), price, id FROM cart GROUP BY team") 167 | shopLen = len(shoppingCart) 168 | for i in range(shopLen): 169 | total += shoppingCart[i]["SUM(subTotal)"] 170 | totItems += shoppingCart[i]["SUM(qty)"] 171 | # Turn on "remove success" flag 172 | display = 1 173 | # Render shopping cart 174 | return render_template ("cart.html", shoppingCart=shoppingCart, shopLen=shopLen, total=total, totItems=totItems, display=display, session=session ) 175 | 176 | 177 | @app.route("/login/", methods=["GET"]) 178 | def login(): 179 | return render_template("login.html") 180 | 181 | 182 | @app.route("/new/", methods=["GET"]) 183 | def new(): 184 | # Render log in page 185 | return render_template("new.html") 186 | 187 | 188 | @app.route("/logged/", methods=["POST"] ) 189 | def logged(): 190 | # Get log in info from log in form 191 | user = request.form["username"].lower() 192 | pwd = request.form["password"] 193 | #pwd = str(sha1(request.form["password"].encode('utf-8')).hexdigest()) 194 | # Make sure form input is not blank and re-render log in page if blank 195 | if user == "" or pwd == "": 196 | return render_template ( "login.html" ) 197 | # Find out if info in form matches a record in user database 198 | query = "SELECT * FROM users WHERE username = :user AND password = :pwd" 199 | rows = db.execute ( query, user=user, pwd=pwd ) 200 | 201 | # If username and password match a record in database, set session variables 202 | if len(rows) == 1: 203 | session['user'] = user 204 | session['time'] = datetime.now( ) 205 | session['uid'] = rows[0]["id"] 206 | # Redirect to Home Page 207 | if 'user' in session: 208 | return redirect ( "/" ) 209 | # If username is not in the database return the log in page 210 | return render_template ( "login.html", msg="Wrong username or password." ) 211 | 212 | 213 | @app.route("/history/") 214 | def history(): 215 | # Initialize shopping cart variables 216 | shoppingCart = [] 217 | shopLen = len(shoppingCart) 218 | totItems, total, display = 0, 0, 0 219 | # Retrieve all shirts ever bought by current user 220 | myShirts = db.execute("SELECT * FROM purchases WHERE uid=:uid", uid=session["uid"]) 221 | myShirtsLen = len(myShirts) 222 | # Render table with shopping history of current user 223 | return render_template("history.html", shoppingCart=shoppingCart, shopLen=shopLen, total=total, totItems=totItems, display=display, session=session, myShirts=myShirts, myShirtsLen=myShirtsLen) 224 | 225 | 226 | @app.route("/logout/") 227 | def logout(): 228 | # clear shopping cart 229 | db.execute("DELETE from cart") 230 | # Forget any user_id 231 | session.clear() 232 | # Redirect user to login form 233 | return redirect("/") 234 | 235 | 236 | @app.route("/register/", methods=["POST"] ) 237 | def registration(): 238 | # Get info from form 239 | username = request.form["username"] 240 | password = request.form["password"] 241 | confirm = request.form["confirm"] 242 | fname = request.form["fname"] 243 | lname = request.form["lname"] 244 | email = request.form["email"] 245 | # See if username already in the database 246 | rows = db.execute( "SELECT * FROM users WHERE username = :username ", username = username ) 247 | # If username already exists, alert user 248 | if len( rows ) > 0: 249 | return render_template ( "new.html", msg="Username already exists!" ) 250 | # If new user, upload his/her info into the users database 251 | new = db.execute ( "INSERT INTO users (username, password, fname, lname, email) VALUES (:username, :password, :fname, :lname, :email)", 252 | username=username, password=password, fname=fname, lname=lname, email=email ) 253 | # Render login template 254 | return render_template ( "login.html" ) 255 | 256 | 257 | @app.route("/cart/") 258 | def cart(): 259 | if 'user' in session: 260 | # Clear shopping cart variables 261 | totItems, total, display = 0, 0, 0 262 | # Grab info currently in database 263 | shoppingCart = db.execute("SELECT team, image, SUM(qty), SUM(subTotal), price, id FROM cart GROUP BY team") 264 | # Get variable values 265 | shopLen = len(shoppingCart) 266 | for i in range(shopLen): 267 | total += shoppingCart[i]["SUM(subTotal)"] 268 | totItems += shoppingCart[i]["SUM(qty)"] 269 | # Render shopping cart 270 | return render_template("cart.html", shoppingCart=shoppingCart, shopLen=shopLen, total=total, totItems=totItems, display=display, session=session) 271 | 272 | 273 | # @app.errorhandler(404) 274 | # def pageNotFound( e ): 275 | # if 'user' in session: 276 | # return render_template ( "404.html", session=session ) 277 | # return render_template ( "404.html" ), 404 278 | 279 | 280 | # Only needed if Flask run is not used to execute the server 281 | #if __name__ == "__main__": 282 | # app.run( host='0.0.0.0', port=8080 ) 283 | -------------------------------------------------------------------------------- /data.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariobox/flask-ecomm/5c11d61c3e0b40d0706b98a5f8ccea3fb438f4d7/data.db -------------------------------------------------------------------------------- /static/css/custom.css: -------------------------------------------------------------------------------- 1 | .card:hover { 2 | border-color: #999; 3 | box-shadow: 1px 2px #999; 4 | } 5 | 6 | .card { 7 | margin-bottom: 1em; 8 | } 9 | 10 | .price { 11 | color: seagreen; 12 | font-weight: bold; 13 | } 14 | 15 | .price:before { 16 | content: '$'; 17 | } 18 | 19 | .shirt { 20 | margin-bottom: 10px; 21 | width: 200px; 22 | } 23 | 24 | .stepper-input{ 25 | display: flex; 26 | display: -webkit-flex; 27 | color: #222; 28 | max-width: 120px; 29 | margin: 10px auto; 30 | text-align: center; 31 | } 32 | 33 | header { 34 | margin-bottom: 50px; 35 | } 36 | 37 | .shirtCart { 38 | width: 25px; 39 | } 40 | 41 | .add { 42 | text-transform: uppercase; 43 | font-size: 0.8em; 44 | font-weight: bold; 45 | color: white; 46 | } 47 | 48 | .checkout { 49 | text-transform: uppercase; 50 | font-size: 0.8em; 51 | font-weight: bold; 52 | } 53 | 54 | .add:hover { 55 | background-color: deepskyblue; 56 | border-color: deepskyblue; 57 | } 58 | 59 | tr { 60 | text-align: center; 61 | } 62 | 63 | .modal-header { 64 | border-bottom: 0px; 65 | } 66 | 67 | .counter { 68 | font-size: 0.6em; 69 | margin-left: 1em; 70 | font-weight: bold; 71 | } 72 | 73 | 74 | .increment, 75 | .decrement{ 76 | height: 24px; 77 | width: 24px; 78 | border: 1px solid #222; 79 | text-align: center; 80 | box-sizing: border-box; 81 | border-radius: 50%; 82 | text-decoration: none; 83 | color: #222; 84 | font-size: 24px; 85 | line-height: 22px; 86 | display: inline-block; 87 | cursor: pointer; 88 | } 89 | 90 | .decrement:hover, 91 | .increment:hover { 92 | color: green; 93 | } 94 | 95 | .decrement:active, 96 | .increment:active { 97 | background-color: green; 98 | color: white; 99 | } 100 | 101 | 102 | .quantity{ 103 | height: 24px; 104 | width: 48px; 105 | text-align: center; 106 | margin: 0 12px; 107 | border-radius: 2px; 108 | border: 1px solid #222; 109 | 110 | } -------------------------------------------------------------------------------- /static/img/argentina.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariobox/flask-ecomm/5c11d61c3e0b40d0706b98a5f8ccea3fb438f4d7/static/img/argentina.png -------------------------------------------------------------------------------- /static/img/belgium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariobox/flask-ecomm/5c11d61c3e0b40d0706b98a5f8ccea3fb438f4d7/static/img/belgium.png -------------------------------------------------------------------------------- /static/img/bj.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariobox/flask-ecomm/5c11d61c3e0b40d0706b98a5f8ccea3fb438f4d7/static/img/bj.jpg -------------------------------------------------------------------------------- /static/img/brasil.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariobox/flask-ecomm/5c11d61c3e0b40d0706b98a5f8ccea3fb438f4d7/static/img/brasil.jpg -------------------------------------------------------------------------------- /static/img/colombia.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariobox/flask-ecomm/5c11d61c3e0b40d0706b98a5f8ccea3fb438f4d7/static/img/colombia.jpg -------------------------------------------------------------------------------- /static/img/cor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariobox/flask-ecomm/5c11d61c3e0b40d0706b98a5f8ccea3fb438f4d7/static/img/cor.png -------------------------------------------------------------------------------- /static/img/costarica.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariobox/flask-ecomm/5c11d61c3e0b40d0706b98a5f8ccea3fb438f4d7/static/img/costarica.jpg -------------------------------------------------------------------------------- /static/img/croacia.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariobox/flask-ecomm/5c11d61c3e0b40d0706b98a5f8ccea3fb438f4d7/static/img/croacia.jpg -------------------------------------------------------------------------------- /static/img/egypt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariobox/flask-ecomm/5c11d61c3e0b40d0706b98a5f8ccea3fb438f4d7/static/img/egypt.jpg -------------------------------------------------------------------------------- /static/img/england.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariobox/flask-ecomm/5c11d61c3e0b40d0706b98a5f8ccea3fb438f4d7/static/img/england.jpg -------------------------------------------------------------------------------- /static/img/fcb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariobox/flask-ecomm/5c11d61c3e0b40d0706b98a5f8ccea3fb438f4d7/static/img/fcb.jpg -------------------------------------------------------------------------------- /static/img/fla.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariobox/flask-ecomm/5c11d61c3e0b40d0706b98a5f8ccea3fb438f4d7/static/img/fla.png -------------------------------------------------------------------------------- /static/img/france.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariobox/flask-ecomm/5c11d61c3e0b40d0706b98a5f8ccea3fb438f4d7/static/img/france.jpg -------------------------------------------------------------------------------- /static/img/germany.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariobox/flask-ecomm/5c11d61c3e0b40d0706b98a5f8ccea3fb438f4d7/static/img/germany.png -------------------------------------------------------------------------------- /static/img/iceland.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariobox/flask-ecomm/5c11d61c3e0b40d0706b98a5f8ccea3fb438f4d7/static/img/iceland.jpg -------------------------------------------------------------------------------- /static/img/juv.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariobox/flask-ecomm/5c11d61c3e0b40d0706b98a5f8ccea3fb438f4d7/static/img/juv.jpg -------------------------------------------------------------------------------- /static/img/mc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariobox/flask-ecomm/5c11d61c3e0b40d0706b98a5f8ccea3fb438f4d7/static/img/mc.jpg -------------------------------------------------------------------------------- /static/img/mexico.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariobox/flask-ecomm/5c11d61c3e0b40d0706b98a5f8ccea3fb438f4d7/static/img/mexico.jpg -------------------------------------------------------------------------------- /static/img/mil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariobox/flask-ecomm/5c11d61c3e0b40d0706b98a5f8ccea3fb438f4d7/static/img/mil.png -------------------------------------------------------------------------------- /static/img/mu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariobox/flask-ecomm/5c11d61c3e0b40d0706b98a5f8ccea3fb438f4d7/static/img/mu.png -------------------------------------------------------------------------------- /static/img/peru.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariobox/flask-ecomm/5c11d61c3e0b40d0706b98a5f8ccea3fb438f4d7/static/img/peru.jpg -------------------------------------------------------------------------------- /static/img/rm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariobox/flask-ecomm/5c11d61c3e0b40d0706b98a5f8ccea3fb438f4d7/static/img/rm.png -------------------------------------------------------------------------------- /static/img/rp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariobox/flask-ecomm/5c11d61c3e0b40d0706b98a5f8ccea3fb438f4d7/static/img/rp.png -------------------------------------------------------------------------------- /static/img/sale-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariobox/flask-ecomm/5c11d61c3e0b40d0706b98a5f8ccea3fb438f4d7/static/img/sale-icon.png -------------------------------------------------------------------------------- /static/img/senegal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariobox/flask-ecomm/5c11d61c3e0b40d0706b98a5f8ccea3fb438f4d7/static/img/senegal.jpg -------------------------------------------------------------------------------- /static/img/spain.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariobox/flask-ecomm/5c11d61c3e0b40d0706b98a5f8ccea3fb438f4d7/static/img/spain.jpg -------------------------------------------------------------------------------- /static/img/sweden.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariobox/flask-ecomm/5c11d61c3e0b40d0706b98a5f8ccea3fb438f4d7/static/img/sweden.jpg -------------------------------------------------------------------------------- /static/img/uruguay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariobox/flask-ecomm/5c11d61c3e0b40d0706b98a5f8ccea3fb438f4d7/static/img/uruguay.png -------------------------------------------------------------------------------- /static/js/myscripts.js: -------------------------------------------------------------------------------- 1 | $(".target").on("click", function() { 2 | let $button = $(this); 3 | let oldVal = parseInt($button.parent().find("input").val()); 4 | let newVal = 0; 5 | 6 | if ($button.text() == '+') { 7 | newVal = oldVal + 1; 8 | } 9 | 10 | else { 11 | if (oldVal > 0) { 12 | newVal = oldVal - 1; 13 | } 14 | else { 15 | newVal = 0; 16 | } 17 | } 18 | 19 | $button.parent().find("input").val(newVal); 20 | }); 21 | 22 | 23 | 24 | 25 | 26 | $('.addToCart').on("click", function(event) { 27 | console.log('hello'); 28 | if($(this).prev().prev().prev().find("input").val() == '0') { 29 | event.preventDefault(); 30 | $(this).next().next().next().html("You need to select at least one shirt."); 31 | $(this).next().next().next().css("display", "block"); 32 | $(this).next().next().next().delay(3000).slideUp(); 33 | } 34 | 35 | if ($(this).prev().val() == "0") { 36 | event.preventDefault(); 37 | $(this).next().next().next().html("You need to log in to buy."); 38 | $(this).next().next().next().css("display", "block"); 39 | $(this).next().next().next().delay(3000).slideUp(); 40 | } 41 | }); 42 | 43 | 44 | $(".flashMessage").delay(3000).slideUp(); 45 | 46 | -------------------------------------------------------------------------------- /static/js/validate.js: -------------------------------------------------------------------------------- 1 | // The submit button 2 | const SUBMIT = $( "#submit" ); 3 | 4 | // Each of the fields and error message divs 5 | const USERNAME = $( "#username" ); 6 | const USERNAME_MSG = $( "#user-msg" ); 7 | 8 | const PASSWORD = $( "#password" ); 9 | const PASSWORD_MSG = $( "#password-msg" ); 10 | 11 | const CONFIRM = $( "#confirm" ); 12 | const CONFIRM_MSG = $( "#confirm-msg" ); 13 | 14 | const FNAME = $( "#fname" ); 15 | const FNAME_MSG = $( "#fname-msg" ); 16 | 17 | const LNAME = $( "#lname" ); 18 | const LNAME_MSG = $( "#lname-msg" ); 19 | 20 | const EMAIL = $( "#email" ); 21 | const EMAIL_MSG = $( "#email-msg" ); 22 | 23 | /** 24 | * Resets the error message fields and makes the submit 25 | * button visible. 26 | */ 27 | function reset_form ( ) 28 | { 29 | USERNAME_MSG.html( "" ); 30 | USERNAME_MSG.hide(); 31 | PASSWORD_MSG.html( "" ); 32 | PASSWORD_MSG.hide(); 33 | CONFIRM_MSG.html( "" ); 34 | CONFIRM_MSG.hide(); 35 | LNAME_MSG.html( "" ); 36 | LNAME_MSG.hide(); 37 | FNAME_MSG.html( "" ); 38 | FNAME_MSG.hide(); 39 | EMAIL_MSG.html( "" ); 40 | EMAIL_MSG.hide(); 41 | SUBMIT.show(); 42 | } 43 | 44 | /** 45 | * Validates the information in the register form so that 46 | * the server is not required to check this information. 47 | */ 48 | function validate ( ) 49 | { 50 | let valid = true; 51 | reset_form ( ); 52 | SUBMIT.hide(); 53 | 54 | // This currently checks to see if the username is 55 | // present and if it is at least 5 characters in length. 56 | if ( !USERNAME.val() || USERNAME.val().length < 5 ) 57 | { 58 | // Show an invalid input message 59 | USERNAME_MSG.html( "Username must be 5 characters or more" ); 60 | USERNAME_MSG.show(); 61 | // Indicate the type of bad input in the console. 62 | console.log( "Bad username" ); 63 | // Indicate that the form is invalid. 64 | valid = false; 65 | } 66 | // TODO: Add your additional checks here. 67 | 68 | 69 | if ( USERNAME.val() != USERNAME.val().toLowerCase()) 70 | { 71 | USERNAME_MSG.html("Username must be all lowercase"); 72 | USERNAME_MSG.show(); 73 | valid = false; 74 | } 75 | 76 | if ( !PASSWORD.val() || PASSWORD.val().length < 8 ) 77 | { 78 | PASSWORD_MSG.html("Password needs to be at least 8 characters long"); 79 | PASSWORD_MSG.show(); 80 | valid = false; 81 | } 82 | 83 | if ( !CONFIRM.val() || PASSWORD.val() != CONFIRM.val() ) 84 | { 85 | CONFIRM_MSG.html("Passwords don't match"); 86 | CONFIRM_MSG.show(); 87 | valid = false; 88 | } 89 | 90 | if ( !FNAME.val() ) 91 | { 92 | FNAME_MSG.html("First name must not be empty"); 93 | FNAME_MSG.show(); 94 | valid = false; 95 | } 96 | 97 | if ( !LNAME.val() ) 98 | { 99 | LNAME_MSG.html("Last name must not be empty"); 100 | LNAME_MSG.show(); 101 | valid = false; 102 | } 103 | 104 | var x = EMAIL.val().trim(); 105 | var atpos = x.indexOf("@"); 106 | var dotpos = x.lastIndexOf("."); 107 | if ( atpos < 1 || dotpos < atpos + 2 || dotpos + 2 >= x.length ) { 108 | EMAIL_MSG.html("You need to enter a valid email address"); 109 | EMAIL_MSG.show(); 110 | valid = false; 111 | } 112 | 113 | // If the form is valid, reset error messages 114 | if ( valid ) 115 | { 116 | reset_form ( ); 117 | } 118 | } 119 | 120 | // Bind the validate function to the required events. 121 | $(document).ready ( validate ); 122 | USERNAME.change ( validate ); 123 | PASSWORD.change ( validate ); 124 | CONFIRM.change ( validate ); 125 | LNAME.change ( validate ); 126 | FNAME.change ( validate ); 127 | EMAIL.change ( validate ); 128 | 129 | 130 | -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | {% block title %}{% endblock %} 18 | 19 | 20 | 21 | 102 |
103 | 143 |

144 |
145 |
146 | {% if display == 1 %} 147 |
148 | Your item was successfully removed from shopping cart! 149 |
150 | {% endif %} 151 | {% block body %}{% endblock %} 152 | 162 | 163 | 164 | 165 | 167 | 169 | 170 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /templates/cart.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | Soccer Jersey Store - Home 5 | {% endblock %} 6 | 7 | {% block body %} 8 | 9 | 92 |
93 |
94 | 95 | {% endblock %} 96 | -------------------------------------------------------------------------------- /templates/history.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | Soccer Jersey Store - Home 5 | {% endblock %} 6 | 7 | {% block body %} 8 | 9 |
10 |
11 |

Your Shopping History

12 |

Items you've bought in the past.

13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | {% for i in range(myShirtsLen) %} 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {% endfor %} 37 | 38 | 39 |
#ItemTeamQuantityDate
{{ i + 1 }}{{ myShirts[i][{{ myShirts[i]["team"] }}{{ myShirts[i]["quantity"] }}{{ myShirts[i]["date"] }}
40 |
41 |
42 | 43 | 44 | 45 | {% endblock %} 46 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | Soccer Jersey Store - Home 5 | {% endblock %} 6 | 7 | {% block body %} 8 | 9 |
10 | {% for i in range(shirtsLen) %} 11 |
12 |
13 |
14 |
15 | 16 |
{{shirts[i]["team"]}}
17 | {% if shirts[i]["onSale"] %} 18 | 19 |

{{ '{:,.2f}'.format(shirts[i]["onSalePrice"]) }}

20 | {% else %} 21 |

{{ '{:,.2f}'.format(shirts[i]["price"]) }}

22 | {% endif %} 23 |
24 | - 25 | 26 | + 27 |
28 | 29 | {% if not session %} 30 | 31 | {% else %} 32 | 33 | {% endif %} 34 |

35 | 36 |
37 |
38 |
39 |
40 | {% endfor %} 41 |
42 | 43 | 44 | 45 | {% endblock %} 46 | -------------------------------------------------------------------------------- /templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | Soccer Jersey Store - Log In 17 | 18 | 19 |
20 | 25 |

26 |
27 |
28 |
29 |
30 |

Log In to Buy

31 |

{{ msg }}

32 |
33 |
34 |
35 | 36 | 37 | 38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | 46 | 47 | -------------------------------------------------------------------------------- /templates/new.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | Soccer Jersey Store - Register 17 | 18 | 19 |
20 | 25 |

26 |
27 |
28 |
29 |
30 |

Register

31 |

{{msg}}

32 |
33 |

34 |

35 |

36 |

37 |

38 |


39 | 40 | 41 |
42 |
43 |
44 |
45 |
46 | 47 | 48 | 49 | 50 | --------------------------------------------------------------------------------