diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c18dd8d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/src/.streamlit/config.toml b/src/.streamlit/config.toml new file mode 100644 index 0000000..88fd354 --- /dev/null +++ b/src/.streamlit/config.toml @@ -0,0 +1,5 @@ +[theme] +primaryColor="#C00000" +backgroundColor="#FFFFFF" +secondaryBackgroundColor="#F0F2F6" +textColor="#31333F" diff --git a/src/PATHS.py b/src/PATHS.py new file mode 100644 index 0000000..af8883c --- /dev/null +++ b/src/PATHS.py @@ -0,0 +1,17 @@ +NAVBAR_PATHS = { + 'HOME':'home', + 'WISHLIST': 'wishlist', + 'FRIENDLIST': 'friendlist' +} + +FOOTER_PATHS = { + # 'GitHub Repository':'https://github.com/landog893/Gifter-2', + 'Code of Conduct': 'https://github.com/landog893/Gifter-2/blob/main/CODE_OF_CONDUCT.md', + 'MIT License': 'https://github.com/landog893/Gifter-2/blob/main/LICENSE', + "Made with Streamlit": 'https://streamlit.io/' +} + +SETTINGS = { + 'PROFILE':'profile', + 'LOGOUT':'logout' +} \ No newline at end of file diff --git a/src/__pycache__/PATHS.cpython-39.pyc b/src/__pycache__/PATHS.cpython-39.pyc new file mode 100644 index 0000000..32536c6 Binary files /dev/null and b/src/__pycache__/PATHS.cpython-39.pyc differ diff --git a/src/__pycache__/account.cpython-39.pyc b/src/__pycache__/account.cpython-39.pyc index febb34f..f736fe7 100644 Binary files a/src/__pycache__/account.cpython-39.pyc and b/src/__pycache__/account.cpython-39.pyc differ diff --git a/src/__pycache__/account_info.cpython-39.pyc b/src/__pycache__/account_info.cpython-39.pyc index 2e6ea12..90bca32 100644 Binary files a/src/__pycache__/account_info.cpython-39.pyc and b/src/__pycache__/account_info.cpython-39.pyc differ diff --git a/src/__pycache__/item.cpython-39.pyc b/src/__pycache__/item.cpython-39.pyc index a08be78..7dcb6d9 100644 Binary files a/src/__pycache__/item.cpython-39.pyc and b/src/__pycache__/item.cpython-39.pyc differ diff --git a/src/__pycache__/item_manager.cpython-39.pyc b/src/__pycache__/item_manager.cpython-39.pyc index 1acaefa..54561f6 100644 Binary files a/src/__pycache__/item_manager.cpython-39.pyc and b/src/__pycache__/item_manager.cpython-39.pyc differ diff --git a/src/__pycache__/utils.cpython-39.pyc b/src/__pycache__/utils.cpython-39.pyc new file mode 100644 index 0000000..c870cfd Binary files /dev/null and b/src/__pycache__/utils.cpython-39.pyc differ diff --git a/src/account_info.py b/src/account_info.py index a781c3d..370d648 100644 --- a/src/account_info.py +++ b/src/account_info.py @@ -282,6 +282,33 @@ def get_friendlist(self, ID): result_dict = result.values return result_dict[0][6] + def find_id(self, ID) : + query = """Select "Name","Surname","Birthday","UserName","Password","Interests","WishList","FriendList" + From "Account" WHERE "ID" = %s;""" + conn = None + user_info = None + try: + # initializing connection + params = config() + print('Connecting to the PostgreSQL database...') + conn = psycopg2.connect(**params) + cur = conn.cursor() + # execute a statement + cur.execute(query, (ID,)) + user_info = cur.fetchall() + cur.close() + conn.commit() + cur.close() + + except (Exception, psycopg2.DatabaseError) as error: + print(error) + finally: + if conn is not None: + conn.close() + print('Database connection closed.') + + return user_info[0].ID if user_info else 0 + class Friends(AccountInfo): def __init__(self): diff --git a/src/assets/images/gift-flat.ico b/src/assets/images/gift-flat.ico new file mode 100644 index 0000000..0eff60f Binary files /dev/null and b/src/assets/images/gift-flat.ico differ diff --git a/src/assets/images/gift-flat.png b/src/assets/images/gift-flat.png new file mode 100644 index 0000000..f029f02 Binary files /dev/null and b/src/assets/images/gift-flat.png differ diff --git a/src/assets/images/github-logo.png b/src/assets/images/github-logo.png new file mode 100644 index 0000000..9490ffc Binary files /dev/null and b/src/assets/images/github-logo.png differ diff --git a/src/assets/images/profile.png b/src/assets/images/profile.png new file mode 100644 index 0000000..c84a0c7 Binary files /dev/null and b/src/assets/images/profile.png differ diff --git a/src/assets/images/settings.png b/src/assets/images/settings.png new file mode 100644 index 0000000..783cd9f Binary files /dev/null and b/src/assets/images/settings.png differ diff --git a/src/assets/styles.css b/src/assets/styles.css new file mode 100644 index 0000000..e4751ef --- /dev/null +++ b/src/assets/styles.css @@ -0,0 +1,289 @@ +.navbar{ + font-family: 'Trebuchet MS', sans-serif; + position: fixed; + width: 100%; + background: #C00000; + z-index: 99999999999999999999; + left: 0rem; + top: 0rem; + height: 55px; + padding-left: 4rem; +} + + +.navitem{ + float: left; + display: block; + color: #f2f2f2 !important; + text-align: center; + padding: 17px 20px; + text-decoration: none !important; + border-bottom: 2px solid transparent; + font-size: 14px; + font-family: 'Trebuchet MS', sans-serif; +} + +.navitem:hover{ + background-color: rgba(221, 221, 221, 0.233); + height: 55px !important; + +} + + +.settings{ + height:1rem; +} + +.version-span{ + color: white; + position: absolute; + right: 1rem; + bottom: 1.75rem; + opacity: 0.5; + z-index: 9999; + border: 2px solid #333; + border-radius: 25px; + padding-right: 0.5rem; + padding-left: 0.5rem; + background: #333; +} + +.dropbtn { + background-color: transparent; + color: white; + padding: 10px; + font-size: 16px; + height: 55px; + opacity: 0.5; + filter: invert(1); + cursor: pointer; + transition: 0.3s; + display: inline-block; + margin: 0; +} + +.dropbtn:hover, .dropbtn:focus, .dropdown:hover .dropbtn { + opacity:1; +} + +.dropdown { + position: fixed; + display: block; + right: 0rem; + top: 0rem; + padding-left: 0.7rem; + user-select: none; + padding-right: 10px; +} + +.dropdown-content { + visibility: hidden; + position: fixed; + border-radius: 9px; + background-color: #C00000; + min-width: 160px; + overflow: auto; + right:0.3rem; + box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); + z-index: 1; +} + +.dropdown-content a { + color: white; + padding: 12px 16px; + text-decoration: none; + display: block; +} + +.dropdown:hover{ + background-color: rgba(221, 221, 221, 0.233); + background: rgba(221, 221, 221, 0.233); +} + +.dropdown a:hover {background-color: rgba(221, 221, 221, 0.233);} + +iframe{ + height: 0rem; +} + +.stTabs { + display: none; +} + +.navName { + display: inline-block; + color: #f2f2f2 !important; + text-align: center; + text-decoration: none !important; + border-bottom: 2px solid transparent; + font-size: 14px; + font-family: 'Trebuchet MS', sans-serif; + position: static; + right: 4rem; + margin: 0; + +} + +.hide { + display: none !important; +} + +.footer{ + font-family: 'Trebuchet MS', sans-serif; + position: relative; + width: 100%; + background: #ededed; + z-index: 99999999999999999999; + left: 0rem; + bottom: 0rem; + height: 70px; + /* padding-left: 4rem; */ + +} + +.footerlist { + position: absolute; + width: 100%; + text-align: center; + background-color: #ededed; +} + +.footeritem{ + /* float: left; */ + /* display: block; */ + display: inline-block; + color: #000 !important; + text-align: center; + margin: 17px 20px; + text-decoration: none !important; + border-bottom: 2px solid transparent; + font-size: 14px; + font-family: 'Trebuchet MS', sans-serif; + filter: invert(.2); +} + +.footeritem:hover{ + filter: invert(.4); +} + +footer:last-child { + display: none; +} + +.gitHub { + display: inline-block; + width: 30px; + margin-top: -4px; + filter: invert(.2); + +} + +.block-container { + padding-top: 0 !important; + padding-bottom: 0 !important; +} + +[data-testid="stHeader"] { + display: none; +} + +[data-testid="stVerticalBlock"] > :nth-child(7) iframe { + height: unset; +} + +blockquote { + color: rgb(49, 51, 63); + font-family: "Source Sans Pro", sans-serif; + font-weight: bold; + font-size: 1.5rem; + margin: 0; + border: none; +} + +blockquote footer { + display: block !important; + text-align: right; + margin-top: 1rem; +} + +blockquote footer:before { + content: "-"; +} + +.buttonDiv { + margin: 20px 0; +} + +.block-container > div > [data-testid="stVerticalBlock"] { + display: block; +} + +.block-container { + margin-top: 55px; +} + +.show { + display: block !important; + background: pink !important; +} + +button[kind="primary"], button[kind="formSubmit"]{ + background-color: #C00000; + color: white; +} + +button[kind="primary"]:hover, button[kind="formSubmit"]:hover{ + background-color: #c00000a3; + color: white; +} + +button[kind="primary"]:active, button[kind="formSubmit"]:active { + background-color: #c00000a3; + color: white; +} + +.horizontalDiv { + width: unset !important; + display: inline-block; +} + +.initial h2 { + text-align: center; + margin-bottom: 50px; + font-size: 3rem; +} + +.initial .buttonDiv { + text-align: center; +} + +.createaccount [data-testid="stVerticalBlock"] { + display: block; +} + +.createaccount [data-testid="stVerticalBlock"] > div { + margin: 10px 0; +} + +.createaccount [data-testid="stVerticalBlock"] > .horizontalDiv:last-child { + float: right; + margin-right: 10px; +} + +.account .stMarkdown [data-testid="stMarkdownContainer"] > p { + margin-bottom: 3rem; + font-size: 20px; + margin-top: 1rem; +} + +.wishlist [data-testid="stVerticalBlock"] { + display: block; +} + +.wishlist .horizontalDiv { + margin-right: 25px; +} + +.friendlist .horizontalDiv { + margin-right: 25px; +} \ No newline at end of file diff --git a/src/main.py b/src/main.py index b223f57..ff15e5c 100644 --- a/src/main.py +++ b/src/main.py @@ -7,26 +7,44 @@ from account_info import AccountInfo from item import item from datetime import datetime +import streamlit.components.v1 as components +import utils as utl +import requests import re - +from streamlit.components.v1 import html + +extrajs = '''''' + +def horizontalButtons(): + global extrajs + extrajs += ''' + forms = window.parent.document.querySelectorAll('[data-testid="stFormSubmitButton"]'); + for (const element of forms) { + element.classList.add("horizontalDiv"); + element.parentElement.classList.add("horizontalDiv"); + element.parentElement.parentElement.classList.add("horizontalDiv"); + } + ''' email_sent = False def initial_page(): st.header("Gift Finder!") - create = st.button('Create Account') - login = st.button('Log In') + create = st.button('Create Account', type="primary") + login = st.button('Log In', type="primary") if create: st.session_state.runpage = 'createaccount' st.experimental_rerun() if login: st.session_state.runpage = 'login' st.experimental_rerun() + def login_page(): + st.header("Login") form1 = st.form(key='Login form') f_name = form1.text_input('User Name') password = form1.text_input('Enter a password', type='password') - but = form1.form_submit_button('Log in') + but = form1.form_submit_button('Log in', type="primary") if but: accountMan = AccountInfo() info = accountMan.get_account(f_name,password) @@ -47,7 +65,7 @@ def login_page(): st.experimental_rerun() def create_account(): - st.write('Please fill out the form') + # st.write('Please fill out the form') form = st.form(key='Create_form') f_name = form.text_input('First Name:') surname = form.text_input('Last Name:') @@ -57,8 +75,9 @@ def create_account(): username = form.text_input('User Name:') password = form.text_input('Enter a password:', type='password') interest = form.text_input('Interests (please enter them comma seperated):') - but1 = form.form_submit_button('Submit') - + + but1 = form.form_submit_button('Submit', type="primary") + but2 = form.form_submit_button("Back") # check if birthday is valid format format = "%m/%d/%Y" validB = True @@ -109,26 +128,22 @@ def create_account(): st.experimental_rerun() #return account - if st.button('Back'): + if but2: st.session_state.runpage = 'initial' - st.experimental_rerun() + st.experimental_rerun() + horizontalButtons() + def account_page(): acc = st.session_state.account st.header('Welcome ' + acc.name + '!') - st.write("What a beautiful day to gift!") - if st.button('Profile'): - st.session_state.runpage = 'profile' - st.experimental_rerun() - if st.button('Wishlist'): - st.session_state.runpage = 'wishlist' - st.experimental_rerun() - if st.button('Friendlist'): - st.session_state.runpage = 'friendlist' - st.experimental_rerun() - if st.button('Logout'): - st.session_state.runpage = 'initial' - st.experimental_rerun() + + st.write("Quote of the day:") + if 'response' not in st.session_state: + st.session_state.response = requests.get('https://zenquotes.io/api/today') + st.markdown(st.session_state.response.json()[0]["h"].replace('—', ''), unsafe_allow_html=True) + + # check if whether a notification email needs to be sent today (is it 1 week before the account's birthday) global email_sent @@ -171,7 +186,7 @@ def profile_page(): st.write('Email: ' + acc.email) st.write('Email Notifications: ' + acc.notifications) st.write('Interests: ' + (acc.interests).replace("\"", "")) - if st.button("Edit Profile"): + if st.button("Edit Profile", type="primary"): st.session_state.runpage = 'editprofile' st.experimental_rerun() if st.button("Back"): @@ -200,7 +215,7 @@ def editprofile_page(): case = -1 chars = set("~!@#$%^&*()_+=") - if form.form_submit_button('Update'): + if form.form_submit_button('Update', type="primary"): # check if birthday is valid format format = "%m/%d/%Y" validB = True @@ -293,18 +308,32 @@ def wishlist_page(): j = j + 1 # st.table(df) - if st.button('Add item'): + if st.button('Add item', type="primary"): st.session_state.runpage = 'additem' st.experimental_rerun() - # if st.button('Modify item'): - # st.session_state.runpage = 'modifyitem' - # st.experimental_rerun() - # if st.button('Remove item'): - # st.session_state.runpage = 'deleteitem' - # st.experimental_rerun() + if st.button('Modify item', type="primary"): + st.session_state.runpage = 'modifyitem' + st.experimental_rerun() + if st.button('Remove item', type="primary"): + st.session_state.runpage = 'deleteitem' + st.experimental_rerun() + if st.button('Back'): st.session_state.runpage = 'account' - st.experimental_rerun() + st.experimental_rerun() + global extrajs + extrajs += ''' + document.addEventListener('DOMContentLoaded', function(event) { + //the event occurred + forms = window.parent.document.querySelectorAll('.stButton'); + for (const element of forms) { + element.classList.add("horizontalDiv"); + element.parentElement.classList.add("horizontalDiv"); + + } + forms[forms.length - 1].parentElement.classList.add("back"); + }) + ''' def additem_page(): form = st.form(key='AddItemForm') @@ -315,7 +344,7 @@ def additem_page(): case = -1 chars = set("~!@#$%^&*()_+=") - if form.form_submit_button('Add item'): + if form.form_submit_button('Add item', type="primary"): if title == "": case = 0 else: @@ -375,16 +404,17 @@ def modifyitem_page(): # except ValueError: # case = 1 - # if case == 0: st.error("Item ID does not exist") - # elif case == 1: st.error("Item ID must be an integer") - # else: + + if case == 0: st.error("Item ID does not exist") + elif case == 1: st.error("Item ID must be an integer") + else: form = st.form(key='ModifyItemForm') title = form.text_input('Title:', value= i.title, placeholder= i.title) desc = form.text_input('Description', value= i.desc, placeholder= i.desc) link = form.text_input('Link', value= i.link, placeholder= i.link) cost = form.text_input('Cost', value= i.cost, placeholder= i.cost) chars = set("~!@#$%^&*()_+=") - if form.form_submit_button('Modify item'): + if form.form_submit_button('Modify item', type="primary"): if title == "": st.error("Title is not nullable") elif any((c in chars) for c in title): @@ -398,7 +428,7 @@ def modifyitem_page(): st.experimental_rerun() except ValueError: st.error("Cost must be a number") - if st.button('Back'): + if st.button('Back', type="primary"): st.session_state.runpage = 'wishlist' st.experimental_rerun() @@ -412,7 +442,7 @@ def deleteitem_page(): # id =form.text_input('Please enter ID of the item you want to delete', value=items[0]) # case = -1 - # if form.form_submit_button('Delete item'): + # if form.form_submit_button('Delete item', type="primary"): # try: # i = item(ID=int(id)) # except ValueError: @@ -461,6 +491,7 @@ def friendlist_page(): if friendlist != 'NaN': friendlist = friendlist.split(',') friendobj = [Account(ID=int(f)) for f in friendlist if f.isnumeric()] + # friendName = [f.name for f in friendobj] # friendSur = [f.surname for f in friendobj] # df = pd.DataFrame(list(zip(friendlist,friendName,friendSur)), columns=('ID', 'First Name', 'Last Name')) @@ -493,18 +524,31 @@ def friendlist_page(): j = j + 1 - # if st.button('View Wishlist of friend'): + # if st.button('View Wishlist of friend', type="primary"): # st.session_state.runpage = 'friendwishlist' # st.experimental_rerun() - if st.button('Add friend'): + if st.button('Add friend', type="primary"): st.session_state.runpage = 'addfriend' st.experimental_rerun() - # if st.button('Delete friend'): + # if st.button('Delete friend', type="primary"): # st.session_state.runpage = 'deletefriend' # st.experimental_rerun() - if st.button('Back'): + if st.button('Back', type="primary"): st.session_state.runpage = 'account' st.experimental_rerun() + global extrajs + extrajs += ''' + document.addEventListener('DOMContentLoaded', function(event) { + //the event occurred + forms = window.parent.document.querySelectorAll('.stButton'); + for (const element of forms) { + element.classList.add("horizontalDiv"); + element.parentElement.classList.add("horizontalDiv"); + + } + forms[forms.length - 1].parentElement.classList.add("back"); + }) + ''' def viewwishlist_page(): acc = st.session_state.account @@ -520,6 +564,7 @@ def viewwishlist_page(): item_objs = [item(ID=id) for id in items] except ValueError: st.error("This ID doesn't have any wishlist") + item_titles = [(i.title).replace("\"", "") for i in item_objs] @@ -539,21 +584,15 @@ def addfriend_page(): acc = st.session_state.account friendlist = acc.friendlist form = st.form(key='addfriend') - id =form.text_input('Please enter ID of the friend') - case = -1 + username =form.text_input('Please enter the username of the friend') + id = -1 - if form.form_submit_button('Add friend'): - try: - friend = Account(ID=int(id)) - except ValueError: - case = 0 - - try: int(id) - except ValueError: - case = 1 + if form.form_submit_button('Add friend', type="primary"): + + accountMan = AccountInfo() + id = accountMan.find_id(username) - if case == 0: st.error("Friend ID does not exist") - elif case == 1: st.error("Friend ID must be an integer") + if id == 0: st.error("Friend Username does not exist") else: if friendlist: friendlist += ',' + str(id) @@ -588,11 +627,12 @@ def deletefriend_page(): print(id) # case = -1 - # if form.form_submit_button('Delete friend'): + # if form.form_submit_button('Delete friend', type="primary"): # try: # Account(ID=int(id)) # except ValueError: # case = 0 + # try: int(id) # except ValueError: @@ -623,12 +663,54 @@ def deletefriend_page(): st.session_state.runpage = 'friendlist' st.experimental_rerun() +if 'account' not in st.session_state or st.session_state.runpage == 'initial': + st.session_state.account = 'None' -if 'runpage' not in st.session_state: +if 'runpage' not in st.session_state or (st.session_state.account == 'None' and not st.session_state.runpage == 'login' and not st.session_state.runpage == 'createaccount'): st.session_state.runpage = 'initial' -if 'account' not in st.session_state: - st.session_state.account = 'None' +# st.set_page_config(layout="wide", page_title='Navbar sample') +st.set_page_config(page_title='Gifter 2', page_icon='assets/images/gift-flat.ico') +st.set_option('deprecation.showPyplotGlobalUse', False) +utl.inject_custom_css() +utl.navbar_component(st.session_state.account) + + +navtab, tab2= st.tabs(["Navtab", "test"]) + +with navtab: + if st.button('home'): + st.session_state.runpage = 'account' + st.experimental_rerun() + if st.button('wishlist'): + st.session_state.runpage = 'wishlist' + st.experimental_rerun() + if st.button('friendlist'): + st.session_state.runpage = 'friendlist' + st.experimental_rerun() + if st.button('acount'): + st.session_state.runpage = 'profile' + st.experimental_rerun() + if st.button('logout'): + st.session_state.runpage = 'initial' + del st.session_state["account"] + st.experimental_rerun() +with tab2: + st.header("Placeholder") + + + +extrajs = ''' + buttonDivs = window.parent.document.querySelectorAll('[data-testid="stVerticalBlock"] > [data-stale="false"] > .stButton'); + while (!buttonDivs) { + buttonDivs = window.parent.document.querySelectorAll('[data-testid="stVerticalBlock"] > [data-stale="false"] > .stButton'); + } + for (const element of buttonDivs) { + element.parentElement.classList.add("but"); + } + ''' + +# navigation() if st.session_state.runpage == 'initial': initial_page() @@ -658,3 +740,19 @@ def deletefriend_page(): addfriend_page() elif st.session_state.runpage == 'deletefriend': deletefriend_page() + +extrajs += ''' + +''' +js = ''' + + ''' + html(js) + + +def footer_component(): + with open("src/assets/images/github-logo.png", "rb") as image_file: + image_as_base64 = base64.b64encode(image_file.read()) + + footer_items = '' + for key, value in FOOTER_PATHS.items(): + footer_items += (f'{key}') +# + component = rf''' + + ''' + st.markdown(component, unsafe_allow_html=True) + js = ''' + + ''' + html(js) \ No newline at end of file