From 3b82c77f3492b861e97f843c77f9daf5d7648452 Mon Sep 17 00:00:00 2001 From: nn2-minh <Nguyen12.Minh@live.uwe.ac.uk> Date: Wed, 2 Apr 2025 23:54:11 +0700 Subject: [PATCH] Modified the front end of the app --- app/backend/routes/shop.py | 13 +- app/frontend/components/auth/login.py | 137 ++++-- app/frontend/components/auth/register.py | 237 ++++++++--- app/frontend/components/dashboard.py | 398 +++++++++++------- .../components/product/view_product.py | 180 ++++++++ app/frontend/components/shop/create_shop.py | 293 +++++++++---- app/frontend/components/shop/view_shop.py | 332 ++++++++++++--- app/frontend/components/user_details.py | 304 ++++++------- app/frontend/components/user_orders.py | 347 ++++++++------- app/frontend/main.py | 4 +- 10 files changed, 1551 insertions(+), 694 deletions(-) create mode 100644 app/frontend/components/product/view_product.py diff --git a/app/backend/routes/shop.py b/app/backend/routes/shop.py index d5a651a..a545b83 100644 --- a/app/backend/routes/shop.py +++ b/app/backend/routes/shop.py @@ -1,7 +1,7 @@ from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form from sqlmodel import Session, func from geopy.geocoders import Nominatim -from backend.models.models import Shop, User, Order +from backend.models.models import Shop, User, Order, Product from backend.schemas.shop import ShopRead from backend.database import get_session from backend.routes.auth import get_current_user @@ -81,12 +81,19 @@ def get_all_shops(order: str = "desc", session: Session = Depends(get_session)): return shops -@router.get("/get/{shop_id}", response_model=ShopRead) +@router.get("/get/{shop_id}", response_model=dict) def read_shop(shop_id: int, session: Session = Depends(get_session)): shop = session.get(Shop, shop_id) if not shop: raise HTTPException(status_code=404, detail="Shop not found") - return shop + + # Query products related to the shop + products = session.query(Product).filter(Product.shop_id == shop_id).all() + + return { + "shop": shop, + "products": products + } @router.put("/put/{shop_id}", response_model=ShopRead) diff --git a/app/frontend/components/auth/login.py b/app/frontend/components/auth/login.py index bc8da11..f44c06f 100644 --- a/app/frontend/components/auth/login.py +++ b/app/frontend/components/auth/login.py @@ -4,26 +4,44 @@ from tkinter import messagebox from PIL import Image from utils.api_requests import login_api +SHOPPING = "#00c1ff" +DARK_BG = "#1f1f1f" +CARD_BG = "#2b2b2b" def login_frame(parent, switch_func, API_URL): - # Overall container - container = ctk.CTkFrame(parent, fg_color="#F3F4F6") # Light gray background + # Overall container with dark background + container = ctk.CTkFrame(parent, fg_color=DARK_BG) container.grid_columnconfigure(0, weight=1) container.grid_columnconfigure(1, weight=1) container.grid_rowconfigure(0, weight=1) # Left image frame - left_frame = ctk.CTkFrame(container, fg_color="transparent") - left_frame.grid(row=0, column=0, sticky="nsew") + left_frame = ctk.CTkFrame(container, fg_color=CARD_BG, corner_radius=15) + left_frame.grid(row=0, column=0, sticky="nsew", padx=(20, 10), pady=20) - image_path = os.path.abspath("app/static/front_end_img/login.jpg") - img = ctk.CTkImage(light_image=Image.open(image_path), size=(800, 800)) - image_label = ctk.CTkLabel(left_frame, image=img, text="") - image_label.place(relwidth=1, relheight=1) + # App branding + brand_frame = ctk.CTkFrame(left_frame, fg_color="transparent") + brand_frame.place(relx=0.5, rely=0.5, anchor="center") + + app_name = ctk.CTkLabel( + brand_frame, + text="Shopping App", + font=("Helvetica", 40, "bold"), + text_color=SHOPPING + ) + app_name.pack() + + app_slogan = ctk.CTkLabel( + brand_frame, + text="Your One-Stop Shopping Destination", + font=("Helvetica", 16), + text_color="white" + ) + app_slogan.pack(pady=(10, 30)) # Right login frame - right_frame = ctk.CTkFrame(container, fg_color="white", corner_radius=20) - right_frame.grid(row=0, column=1, sticky="nsew", padx=50, pady=100) + right_frame = ctk.CTkFrame(container, fg_color=CARD_BG, corner_radius=15) + right_frame.grid(row=0, column=1, sticky="nsew", padx=(10, 20), pady=20) right_frame.grid_columnconfigure(0, weight=1) # Login Function @@ -46,56 +64,103 @@ def login_frame(parent, switch_func, API_URL): "Login Failed", response_data.get("detail", "Invalid credentials") ) + # Login form container + form_frame = ctk.CTkFrame(right_frame, fg_color="transparent") + form_frame.place(relx=0.5, rely=0.5, anchor="center") + # Title ctk.CTkLabel( - right_frame, - text="Login", - font=ctk.CTkFont("Helvetica", size=26, weight="bold"), - text_color="#111827", - ).pack(pady=(20, 10)) + form_frame, + text="Welcome Back", + font=("Helvetica", 32, "bold"), + text_color=SHOPPING + ).pack(pady=(0, 10)) + + ctk.CTkLabel( + form_frame, + text="Sign in to your account", + font=("Helvetica", 14), + text_color="gray" + ).pack(pady=(0, 20)) # Email + email_frame = ctk.CTkFrame(form_frame, fg_color="transparent") + email_frame.pack(fill="x", pady=(0, 15)) + ctk.CTkLabel( - right_frame, text="Email", font=("Helvetica", 14), text_color="#374151" - ).pack(pady=(10, 5)) + email_frame, + text="Email", + font=("Helvetica", 14), + text_color="white" + ).pack(anchor="w") + entry_email = ctk.CTkEntry( - right_frame, height=40, corner_radius=10, placeholder_text="Enter your email" + email_frame, + height=40, + corner_radius=8, + placeholder_text="Enter your email", + fg_color="#3b3b3b", + border_color=SHOPPING, + text_color="white" ) - entry_email.pack(pady=5, padx=20, fill="x") + entry_email.pack(fill="x", pady=(5, 0)) # Password + password_frame = ctk.CTkFrame(form_frame, fg_color="transparent") + password_frame.pack(fill="x", pady=(0, 20)) + ctk.CTkLabel( - right_frame, text="Password", font=("Helvetica", 14), text_color="#374151" - ).pack(pady=(10, 5)) + password_frame, + text="Password", + font=("Helvetica", 14), + text_color="white" + ).pack(anchor="w") + entry_password = ctk.CTkEntry( - right_frame, + password_frame, height=40, show="*", - corner_radius=10, + corner_radius=8, placeholder_text="Enter your password", + fg_color="#3b3b3b", + border_color=SHOPPING, + text_color="white" ) - entry_password.pack(pady=5, padx=20, fill="x") + entry_password.pack(fill="x", pady=(5, 0)) # Login Button ctk.CTkButton( - right_frame, - text="Login", + form_frame, + text="Sign In", command=login, - corner_radius=10, + corner_radius=8, height=45, - font=("Helvetica", 14), - fg_color="#2563EB", - hover_color="#1E40AF", - ).pack(pady=20, padx=20, fill="x") + font=("Helvetica", 14, "bold"), + fg_color=SHOPPING, + hover_color="#0096ff", + ).pack(fill="x", pady=(0, 15)) # Register Redirect + register_frame = ctk.CTkFrame(form_frame, fg_color="transparent") + register_frame.pack(fill="x") + + ctk.CTkLabel( + register_frame, + text="Don't have an account?", + font=("Helvetica", 12), + text_color="gray" + ).pack(side="left", padx=(0, 5)) + ctk.CTkButton( - right_frame, - text="Don't have an account? Register", + register_frame, + text="Register", command=lambda: switch_func("register"), fg_color="transparent", - hover_color="#E5E7EB", - text_color="#2563EB", - ).pack(pady=(5, 20)) + hover_color="#3b3b3b", + text_color=SHOPPING, + font=("Helvetica", 12, "bold"), + width=70, + height=25 + ).pack(side="left") return container diff --git a/app/frontend/components/auth/register.py b/app/frontend/components/auth/register.py index 5f92659..6677b2f 100644 --- a/app/frontend/components/auth/register.py +++ b/app/frontend/components/auth/register.py @@ -1,19 +1,45 @@ import customtkinter as ctk from tkinter import messagebox -from utils.api_requests import register_api # Import the API function +from utils.api_requests import register_api +SHOPPING = "#00c1ff" +DARK_BG = "#1f1f1f" +CARD_BG = "#2b2b2b" def register_frame(parent, switch_func, API_URL): - # Create a full-window container with a light gray background - container = ctk.CTkFrame(parent, fg_color="#F3F4F6") - container.pack(expand=True, fill="both") - - # Center the register panel (the white card) in the middle of the container. - # Width and height are set to ensure a consistent size. - register_panel = ctk.CTkFrame( - container, fg_color="white", corner_radius=20, width=400, height=500 + # Create a full-window container with dark background + container = ctk.CTkFrame(parent, fg_color=DARK_BG) + container.grid_columnconfigure(0, weight=1) + container.grid_columnconfigure(1, weight=1) + container.grid_rowconfigure(0, weight=1) + + # Left branding frame + left_frame = ctk.CTkFrame(container, fg_color=CARD_BG, corner_radius=15) + left_frame.grid(row=0, column=0, sticky="nsew", padx=(20, 10), pady=20) + + # App branding + brand_frame = ctk.CTkFrame(left_frame, fg_color="transparent") + brand_frame.place(relx=0.5, rely=0.5, anchor="center") + + app_name = ctk.CTkLabel( + brand_frame, + text="Shopping App", + font=("Helvetica", 40, "bold"), + text_color=SHOPPING ) - register_panel.place(relx=0.5, rely=0.5, anchor="center") + app_name.pack() + + app_slogan = ctk.CTkLabel( + brand_frame, + text="Join Our Shopping Community", + font=("Helvetica", 16), + text_color="white" + ) + app_slogan.pack(pady=(10, 30)) + + # Right register frame + right_frame = ctk.CTkFrame(container, fg_color=CARD_BG, corner_radius=15) + right_frame.grid(row=0, column=1, sticky="nsew", padx=(10, 20), pady=20) def register(): username = entry_username.get() @@ -22,13 +48,7 @@ def register_frame(parent, switch_func, API_URL): password = entry_password.get() confirm_password = entry_confirm_password.get() - if ( - not username - or not email - or not phone_number - or not password - or not confirm_password - ): + if not all([username, email, phone_number, password, confirm_password]): messagebox.showwarning("Input Error", "All fields are required!") return @@ -36,7 +56,6 @@ def register_frame(parent, switch_func, API_URL): messagebox.showerror("Password Error", "Passwords do not match!") return - # Call the API function from register_api.py status_code, response_data = register_api( username, email, phone_number, password, API_URL ) @@ -49,82 +68,170 @@ def register_frame(parent, switch_func, API_URL): "Registration Failed", response_data.get("detail", "Unknown error") ) - # Title for the registration form + # Register form container + form_frame = ctk.CTkFrame(right_frame, fg_color="transparent") + form_frame.place(relx=0.5, rely=0.5, anchor="center") + + # Title ctk.CTkLabel( - register_panel, + form_frame, text="Create Account", - font=ctk.CTkFont("Helvetica", size=26, weight="bold"), - text_color="#111827", - ).pack(pady=(20, 10)) + font=("Helvetica", 32, "bold"), + text_color=SHOPPING + ).pack(pady=(0, 10)) + + ctk.CTkLabel( + form_frame, + text="Fill in your details to register", + font=("Helvetica", 14), + text_color="gray" + ).pack(pady=(0, 20)) + + # Username + username_frame = ctk.CTkFrame(form_frame, fg_color="transparent") + username_frame.pack(fill="x", pady=(0, 15)) + + ctk.CTkLabel( + username_frame, + text="Username", + font=("Helvetica", 14), + text_color="white" + ).pack(anchor="w") - # Username Entry entry_username = ctk.CTkEntry( - register_panel, - placeholder_text="Username", + username_frame, height=40, - corner_radius=10, - width=250, + corner_radius=8, + placeholder_text="Enter your username", + fg_color="#3b3b3b", + border_color=SHOPPING, + text_color="white" ) - entry_username.pack(pady=10, padx=20, fill="x") + entry_username.pack(fill="x", pady=(5, 0)) + + # Email + email_frame = ctk.CTkFrame(form_frame, fg_color="transparent") + email_frame.pack(fill="x", pady=(0, 15)) + + ctk.CTkLabel( + email_frame, + text="Email", + font=("Helvetica", 14), + text_color="white" + ).pack(anchor="w") - # Email Entry entry_email = ctk.CTkEntry( - register_panel, placeholder_text="Email", height=40, corner_radius=10, width=250 + email_frame, + height=40, + corner_radius=8, + placeholder_text="Enter your email", + fg_color="#3b3b3b", + border_color=SHOPPING, + text_color="white" ) - entry_email.pack(pady=10, padx=20, fill="x") + entry_email.pack(fill="x", pady=(5, 0)) + + # Phone + phone_frame = ctk.CTkFrame(form_frame, fg_color="transparent") + phone_frame.pack(fill="x", pady=(0, 15)) + + ctk.CTkLabel( + phone_frame, + text="Phone Number", + font=("Helvetica", 14), + text_color="white" + ).pack(anchor="w") - # Phone Number Entry entry_phone = ctk.CTkEntry( - register_panel, - placeholder_text="Phone Number", + phone_frame, height=40, - corner_radius=10, - width=250, + corner_radius=8, + placeholder_text="Enter your phone number", + fg_color="#3b3b3b", + border_color=SHOPPING, + text_color="white" ) - entry_phone.pack(pady=10, padx=20, fill="x") + entry_phone.pack(fill="x", pady=(5, 0)) + + # Password + password_frame = ctk.CTkFrame(form_frame, fg_color="transparent") + password_frame.pack(fill="x", pady=(0, 15)) + + ctk.CTkLabel( + password_frame, + text="Password", + font=("Helvetica", 14), + text_color="white" + ).pack(anchor="w") - # Password Entry entry_password = ctk.CTkEntry( - register_panel, - placeholder_text="Password", - show="*", + password_frame, height=40, - corner_radius=10, - width=250, + corner_radius=8, + show="*", + placeholder_text="Enter your password", + fg_color="#3b3b3b", + border_color=SHOPPING, + text_color="white" ) - entry_password.pack(pady=10, padx=20, fill="x") + entry_password.pack(fill="x", pady=(5, 0)) + + # Confirm Password + confirm_frame = ctk.CTkFrame(form_frame, fg_color="transparent") + confirm_frame.pack(fill="x", pady=(0, 20)) + + ctk.CTkLabel( + confirm_frame, + text="Confirm Password", + font=("Helvetica", 14), + text_color="white" + ).pack(anchor="w") - # Confirm Password Entry entry_confirm_password = ctk.CTkEntry( - register_panel, - placeholder_text="Confirm Password", - show="*", + confirm_frame, height=40, - corner_radius=10, - width=250, + corner_radius=8, + show="*", + placeholder_text="Confirm your password", + fg_color="#3b3b3b", + border_color=SHOPPING, + text_color="white" ) - entry_confirm_password.pack(pady=10, padx=20, fill="x") + entry_confirm_password.pack(fill="x", pady=(5, 0)) # Register Button ctk.CTkButton( - register_panel, - text="Register", + form_frame, + text="Create Account", command=register, - corner_radius=10, + corner_radius=8, height=45, - font=("Helvetica", 14), - fg_color="#2563EB", - hover_color="#1E40AF", - ).pack(pady=20, padx=20, fill="x") + font=("Helvetica", 14, "bold"), + fg_color=SHOPPING, + hover_color="#0096ff", + ).pack(fill="x", pady=(0, 15)) + + # Login Redirect + login_frame = ctk.CTkFrame(form_frame, fg_color="transparent") + login_frame.pack(fill="x") + + ctk.CTkLabel( + login_frame, + text="Already have an account?", + font=("Helvetica", 12), + text_color="gray" + ).pack(side="left", padx=(0, 5)) - # Link to Login Page ctk.CTkButton( - register_panel, - text="Already have an account? Login", + login_frame, + text="Sign In", command=lambda: switch_func("login"), fg_color="transparent", - hover_color="#E5E7EB", - text_color="#2563EB", - ).pack(pady=(5, 20)) + hover_color="#3b3b3b", + text_color=SHOPPING, + font=("Helvetica", 12, "bold"), + width=70, + height=25 + ).pack(side="left") return container diff --git a/app/frontend/components/dashboard.py b/app/frontend/components/dashboard.py index fe5a880..b4bc80b 100644 --- a/app/frontend/components/dashboard.py +++ b/app/frontend/components/dashboard.py @@ -3,9 +3,13 @@ import requests from PIL import Image, ImageTk from tkinter import messagebox import io +# import time +# import threading SHOPPING = "#00c1ff" -BACKEND_HOST = "http://127.0.0.1:8000" # Adjust if needed +DARK_BG = "#1f1f1f" +CARD_BG = "#2b2b2b" +BACKEND_HOST = "http://127.0.0.1:8000" def fix_url(url): @@ -18,38 +22,79 @@ def fix_url(url): # If the URL starts with "app/static/", remove that part. prefix = "app/static/" if url.startswith(prefix): - url = url[len(prefix) :] + url = url[len(prefix):] # Prepend the public URL return f"{BACKEND_HOST}/static/{url}" def dashboard_frame(parent, switch_func, API_URL, token): - """ - Main dashboard UI: - - Header (top bar) with logo, search, user, cart - - Middle area with horizontal scroll of top shops - - Bottom area with grid layout of recommended products - """ - frame = ctk.CTkFrame(parent, fg_color="transparent") - - # ------------- HEADER (Top Bar) ------------- - header_frame = ctk.CTkFrame(frame, fg_color=SHOPPING) - header_frame.place(relx=0, rely=0, relwidth=1, relheight=0.1) - - # App Logo (Left) - logo_label = ctk.CTkLabel( - header_frame, - text="Shopping App", + frame = ctk.CTkFrame(parent, fg_color=DARK_BG) + + # ------------- LEFT SIDEBAR ------------- + sidebar = ctk.CTkFrame(frame, fg_color=CARD_BG, width=60) + sidebar.pack(side="left", fill="y") + sidebar.pack_propagate(False) + + # Dashboard Icon + dashboard_icon = ctk.CTkButton( + sidebar, + text="π ", + font=("Helvetica", 20), + fg_color="transparent", text_color="white", - font=("Helvetica", 20, "bold"), + hover_color="#3b3b3b", + width=40, + height=40, + command=lambda: switch_func("dashboard") + ) + dashboard_icon.pack(pady=(20, 10)) + + # User Icon + user_icon = ctk.CTkButton( + sidebar, + text="π€", + font=("Helvetica", 20), + fg_color="transparent", + text_color="white", + hover_color="#3b3b3b", + width=40, + height=40, + command=lambda: switch_func("user_details") ) - logo_label.place(relx=0.01, rely=0.1, relwidth=0.15, relheight=0.8) + user_icon.pack(pady=10) + + # Cart Icon + cart_icon = ctk.CTkButton( + sidebar, + text="π", + font=("Helvetica", 20), + fg_color="transparent", + text_color="white", + hover_color="#3b3b3b", + width=40, + height=40, + command=lambda: switch_func("user_orders") + ) + cart_icon.pack(pady=10) + + # ------------- MAIN CONTENT ------------- + main_content = ctk.CTkFrame(frame, fg_color="transparent") + main_content.pack(side="left", fill="both", expand=True) + + # Search bar at top + search_frame = ctk.CTkFrame(main_content, fg_color=CARD_BG, height=60) + search_frame.pack(fill="x", padx=20, pady=10) + search_frame.pack_propagate(False) - # Search bar (Center) search_entry = ctk.CTkEntry( - header_frame, placeholder_text="Search in Shop...", height=30 + search_frame, + placeholder_text="Search for shops and products...", + height=35, + fg_color="#3b3b3b", + text_color="white", + placeholder_text_color="#888888" ) - search_entry.place(relx=0.25, rely=0.25, relwidth=0.45, relheight=0.5) + search_entry.pack(side="left", fill="x", expand=True, padx=(20, 10), pady=12) def perform_search(): """Call an endpoint to search products by keyword.""" @@ -65,7 +110,7 @@ def dashboard_frame(parent, switch_func, API_URL, token): ) if resp.status_code == 200: products = resp.json() - display_products(products, bottom_products_frame) + display_products(products, products_frame) else: messagebox.showerror( "Error", f"Search failed. Status code: {resp.status_code}" @@ -74,181 +119,210 @@ def dashboard_frame(parent, switch_func, API_URL, token): messagebox.showerror("Error", f"Request error: {e}") search_button = ctk.CTkButton( - header_frame, + search_frame, text="Search", - fg_color="white", - text_color="black", - command=perform_search, - ) - search_button.place(relx=0.71, rely=0.25, relwidth=0.08, relheight=0.5) - - # ------------- USER & CART ICONS (Top-Right) ------------- - def open_user_details(): - switch_func("user_details") - - def open_cart_details(): - switch_func("user_orders") - - user_button = ctk.CTkButton( - header_frame, - text="User", - fg_color="white", - text_color="black", - command=open_user_details, - ) - user_button.place(relx=0.82, rely=0.25, relwidth=0.08, relheight=0.5) - - cart_button = ctk.CTkButton( - header_frame, - text="Cart", - fg_color="white", - text_color="black", - command=open_cart_details, - ) - cart_button.place(relx=0.91, rely=0.25, relwidth=0.08, relheight=0.5) - - # ------------- MIDDLE (Featured/Top Shops) ------------- - middle_frame = ctk.CTkFrame(frame, fg_color="transparent") - middle_frame.place(relx=0, rely=0.1, relwidth=1, relheight=0.25) - - featured_label = ctk.CTkLabel( - middle_frame, text="TOP SHOP", font=("Helvetica", 16, "bold") - ) - featured_label.pack(pady=5) - - top_shops_frame = ctk.CTkScrollableFrame( - middle_frame, fg_color="transparent", width=750, height=150 + fg_color=SHOPPING, + text_color="white", + width=100, + height=35, + command=perform_search ) - top_shops_frame.pack(fill="both", expand=True, padx=10, pady=5) - - def create_shop_card(parent, shop_data): - """ - Create a card for a shop with its image on top and name below. - """ - card = ctk.CTkFrame( - parent, corner_radius=5, fg_color="#2b2b2b", width=100, height=130 - ) - card.pack_propagate(False) + search_button.pack(side="right", padx=20) + + # Featured Shops Carousel + carousel_frame = ctk.CTkFrame(main_content, fg_color=CARD_BG, height=300) + carousel_frame.pack(fill="x", padx=20, pady=10) + carousel_frame.pack_propagate(False) + + # Carousel content + carousel_content = ctk.CTkFrame(carousel_frame, fg_color="transparent") + carousel_content.pack(fill="both", expand=True, padx=20, pady=20) + + featured_shops = [] + current_shop_index = 0 + + def create_shop_slide(shop_data): + slide = ctk.CTkFrame(carousel_content, fg_color="transparent") + + # Shop Image + image_frame = ctk.CTkFrame(slide, fg_color="transparent", width=400, height=200) + image_frame.pack(side="left", padx=20) + image_frame.pack_propagate(False) + + image_label = ctk.CTkLabel(image_frame, text="") + image_label.pack(expand=True) - image_label = ctk.CTkLabel(card, text="") - image_label.pack(pady=5) - - # Use "image_url" as returned by the backend, then fix it if needed. image_url = shop_data.get("image_url") if image_url: fixed_url = fix_url(image_url) - print(f"[DEBUG] Fetching shop image from: {fixed_url}") try: resp = requests.get(fixed_url) - print(f"[DEBUG] Status code for shop image: {resp.status_code}") if resp.status_code == 200: - pil_img = Image.open(io.BytesIO(resp.content)).resize((60, 60)) + pil_img = Image.open(io.BytesIO(resp.content)).resize((380, 180)) tk_img = ImageTk.PhotoImage(pil_img) image_label.configure(image=tk_img, text="") image_label.image = tk_img - else: - print( - f"[DEBUG] Failed to fetch shop image. Status: {resp.status_code}" - ) except Exception as e: print(f"[DEBUG] Shop image error: {e}") - else: - print(f"[DEBUG] No 'image_url' found for shop: {shop_data.get('name')}") - name_text = shop_data.get("name", "No Name") - name_label = ctk.CTkLabel( - card, text=name_text, font=("Helvetica", 11, "bold"), wraplength=90 + # Shop Details + details_frame = ctk.CTkFrame(slide, fg_color="transparent") + details_frame.pack(side="left", fill="both", expand=True, padx=20) + + shop_name = ctk.CTkLabel( + details_frame, + text=shop_data.get("name", "Shop Name"), + font=("Helvetica", 24, "bold"), + text_color=SHOPPING + ) + shop_name.pack(anchor="w", pady=(0, 10)) + + shop_desc = ctk.CTkLabel( + details_frame, + text=shop_data.get("description", "No description available."), + font=("Helvetica", 14), + text_color="white", + wraplength=400, + justify="left" + ) + shop_desc.pack(anchor="w", pady=5) + + visit_button = ctk.CTkButton( + details_frame, + text="Visit Shop β", + fg_color=SHOPPING, + text_color="white", + command=lambda: switch_func("view_shop", shop_data.get("id")), + width=120, + height=35 ) - name_label.pack() + visit_button.pack(anchor="w", pady=20) - return card + return slide - def display_shops(shops, container): - for widget in container.winfo_children(): + def update_carousel(): + nonlocal current_shop_index + if not featured_shops: + return + + # Clear current content + for widget in carousel_content.winfo_children(): widget.destroy() - if not shops: - ctk.CTkLabel(container, text="No shops found.").pack(pady=10) - return + # Create and show current slide + current_slide = create_shop_slide(featured_shops[current_shop_index]) + current_slide.pack(fill="both", expand=True) - inner_frame = ctk.CTkFrame(container, fg_color="transparent") - inner_frame.pack(side="top", fill="both", expand=True) + # Update dots + update_carousel_dots() - for shop in shops: - shop_card = create_shop_card(inner_frame, shop) - shop_card.pack(side="left", padx=5, pady=5) + # Schedule next slide + current_shop_index = (current_shop_index + 1) % len(featured_shops) + carousel_content.after(5000, update_carousel) - # ------------- BOTTOM (Products) ------------- - bottom_frame = ctk.CTkFrame(frame, fg_color="transparent") - bottom_frame.place(relx=0, rely=0.35, relwidth=1, relheight=0.65) + # Carousel navigation dots + dots_frame = ctk.CTkFrame(carousel_frame, fg_color="transparent", height=30) + dots_frame.pack(side="bottom", fill="x", pady=10) - recommend_label = ctk.CTkLabel( - bottom_frame, text="TODAY'S RECOMMENDATIONS", font=("Helvetica", 16, "bold") + def update_carousel_dots(): + for widget in dots_frame.winfo_children(): + widget.destroy() + + for i in range(len(featured_shops)): + color = SHOPPING if i == current_shop_index else "gray" + dot = ctk.CTkLabel( + dots_frame, + text="β", + text_color=color, + font=("Helvetica", 16) + ) + dot.pack(side="left", padx=2) + + # Products Section + products_section = ctk.CTkFrame(main_content, fg_color="transparent") + products_section.pack(fill="both", expand=True, padx=20, pady=10) + + products_header = ctk.CTkFrame(products_section, fg_color="transparent") + products_header.pack(fill="x", pady=(0, 10)) + + products_title = ctk.CTkLabel( + products_header, + text="Featured Products", + font=("Helvetica", 20, "bold"), + text_color="white" ) - recommend_label.pack(pady=5) + products_title.pack(side="left") - bottom_products_frame = ctk.CTkScrollableFrame( - bottom_frame, fg_color="transparent", width=750, height=300 + products_frame = ctk.CTkScrollableFrame( + products_section, + fg_color="transparent", + height=400 ) - bottom_products_frame.pack(fill="both", expand=True, padx=10, pady=5) + products_frame.pack(fill="both", expand=True) def create_product_card(parent, product_data): - card_width = 130 - card_height = 210 card = ctk.CTkFrame( parent, - corner_radius=5, - fg_color="#2b2b2b", - width=card_width, - height=card_height, + fg_color=CARD_BG, + corner_radius=10, + width=200, + height=280 ) card.pack_propagate(False) - image_label = ctk.CTkLabel(card, text="") - image_label.pack(pady=5) + # Product Image + image_frame = ctk.CTkFrame(card, fg_color="transparent", height=150) + image_frame.pack(fill="x", padx=10, pady=10) + image_frame.pack_propagate(False) + + image_label = ctk.CTkLabel(image_frame, text="") + image_label.pack(expand=True) product_images = product_data.get("images", []) if product_images: img_url = product_images[0].get("image_url") if img_url: fixed_url = fix_url(img_url) - print(f"[DEBUG] Fetching product image from: {fixed_url}") try: resp = requests.get(fixed_url) - print(f"[DEBUG] Status code for product image: {resp.status_code}") if resp.status_code == 200: - pil_img = Image.open(io.BytesIO(resp.content)).resize((70, 70)) + pil_img = Image.open(io.BytesIO(resp.content)).resize((120, 120)) tk_img = ImageTk.PhotoImage(pil_img) image_label.configure(image=tk_img, text="") image_label.image = tk_img - else: - print( - f"[DEBUG] Failed to fetch product image. Status: {resp.status_code}" - ) except Exception as e: print(f"[DEBUG] Product image error: {e}") - else: - print(f"[DEBUG] No images found for product: {product_data.get('name')}") - name_text = product_data.get("name", "No Name") + # Product Details + details_frame = ctk.CTkFrame(card, fg_color="transparent") + details_frame.pack(fill="both", expand=True, padx=10, pady=(0, 10)) + name_label = ctk.CTkLabel( - card, - text=name_text, - font=("Helvetica", 11, "bold"), - wraplength=120, - anchor="center", - justify="center", + details_frame, + text=product_data.get("name", "No Name"), + font=("Helvetica", 14, "bold"), + wraplength=180, + justify="left" ) - name_label.pack(padx=5, pady=3) + name_label.pack(anchor="w") - price_val = product_data.get("price", 0.0) price_label = ctk.CTkLabel( - card, - text=f"β« {price_val:,.1f}", - font=("Helvetica", 10, "bold"), - text_color="#ff4242", + details_frame, + text=f"β« {product_data.get('price', 0.0):,.1f}", + font=("Helvetica", 14, "bold"), + text_color="#ff4242" + ) + price_label.pack(anchor="w", pady=5) + + view_button = ctk.CTkButton( + details_frame, + text="View Details", + command=lambda: switch_func("view_product", product_data), + fg_color=SHOPPING, + text_color="white", + height=30 ) - price_label.pack(side="bottom", pady=5) + view_button.pack(side="bottom", pady=5) return card @@ -257,26 +331,42 @@ def dashboard_frame(parent, switch_func, API_URL, token): widget.destroy() if not products: - ctk.CTkLabel(container, text="No products found.").pack(pady=10) + no_products_label = ctk.CTkLabel( + container, + text="No products available.", + font=("Helvetica", 14), + text_color="gray" + ) + no_products_label.pack(pady=20) return grid_frame = ctk.CTkFrame(container, fg_color="transparent") grid_frame.pack(fill="both", expand=True) - columns = 7 # 7 products per row - for idx, product in enumerate(products): - row = idx // columns - col = idx % columns + max_cols = 4 + row = 0 + col = 0 + + for product in products: product_card = create_product_card(grid_frame, product) - product_card.grid(row=row, column=col, padx=5, pady=5) + product_card.grid(row=row, column=col, padx=10, pady=10, sticky="nsew") + + col += 1 + if col >= max_cols: + col = 0 + row += 1 + + for i in range(max_cols): + grid_frame.grid_columnconfigure(i, weight=1) def fetch_featured_shops(): headers = {"Authorization": f"Bearer {token}"} try: resp = requests.get(f"{API_URL}/shops/list", headers=headers) if resp.status_code == 200: - shops = resp.json() - display_shops(shops[:100], top_shops_frame) + nonlocal featured_shops + featured_shops = resp.json()[:5] # Get top 5 shops for carousel + update_carousel() else: messagebox.showerror( "Error", @@ -291,7 +381,7 @@ def dashboard_frame(parent, switch_func, API_URL, token): resp = requests.get(f"{API_URL}/product/list", headers=headers) if resp.status_code == 200: products = resp.json() - display_products(products[:20], bottom_products_frame) + display_products(products[:12], products_frame) # Show top 12 products else: messagebox.showerror( "Error", diff --git a/app/frontend/components/product/view_product.py b/app/frontend/components/product/view_product.py new file mode 100644 index 0000000..5fdca7d --- /dev/null +++ b/app/frontend/components/product/view_product.py @@ -0,0 +1,180 @@ +import customtkinter as ctk +import requests +from PIL import Image, ImageTk +from tkinter import messagebox +import io + +BACKEND_HOST = "http://127.0.0.1:8000" # Adjust if needed + + +def fix_url(url): + """ + Fix the URL to ensure it points to the correct static file location. + """ + if url.startswith("http"): + return url + return f"{BACKEND_HOST}/static/{url}" + + +def view_shop_frame(parent, switch_func, API_URL, token, shop_id): + """ + CustomTkinter-based frame to display shop details and products. + """ + frame = ctk.CTkFrame(parent, fg_color="transparent") + + # Fetch shop details and products from the backend + headers = {"Authorization": f"Bearer {token}"} + try: + response = requests.get(f"{API_URL}/shops/get/{shop_id}", headers=headers) + if response.status_code == 200: + data = response.json() + shop_data = data.get("shop", {}) + products = data.get("products", []) + else: + messagebox.showerror("Error", f"Failed to fetch shop data. Status: {response.status_code}") + return frame + except Exception as e: + messagebox.showerror("Error", f"Request error: {e}") + return frame + + # ------------- TOP LAYER (Shop Image and Details) ------------- + top_layer = ctk.CTkFrame(frame, fg_color="transparent") + top_layer.place(relx=0, rely=0, relwidth=1, relheight=0.4) + + # Shop Image + shop_image_label = ctk.CTkLabel(top_layer, text="") + shop_image_label.place(relx=0.05, rely=0.1, relwidth=0.3, relheight=0.8) + + shop_image_url = shop_data.get("image_url") + if shop_image_url: + fixed_url = fix_url(shop_image_url) + try: + resp = requests.get(fixed_url) + if resp.status_code == 200: + pil_img = Image.open(io.BytesIO(resp.content)).resize((200, 200)) + tk_img = ImageTk.PhotoImage(pil_img) + shop_image_label.configure(image=tk_img, text="") + shop_image_label.image = tk_img + except Exception as e: + print(f"[DEBUG] Failed to load shop image: {e}") + + # Shop Details + shop_details_frame = ctk.CTkFrame(top_layer, fg_color="transparent") + shop_details_frame.place(relx=0.4, rely=0.1, relwidth=0.55, relheight=0.8) + + shop_name_label = ctk.CTkLabel( + shop_details_frame, + text=shop_data.get("name", "Shop Name"), + font=("Helvetica", 20, "bold"), + ) + shop_name_label.pack(pady=10) + + shop_description_label = ctk.CTkLabel( + shop_details_frame, + text=shop_data.get("description", "No description available."), + font=("Helvetica", 14), + wraplength=400, + justify="left", + ) + shop_description_label.pack(pady=5) + + shop_address_label = ctk.CTkLabel( + shop_details_frame, + text=f"Address: {shop_data.get('address', 'No address provided')}", + font=("Helvetica", 12), + ) + shop_address_label.pack(pady=5) + + # ------------- BOTTOM LAYER (Products) ------------- + bottom_layer = ctk.CTkFrame(frame, fg_color="transparent") + bottom_layer.place(relx=0, rely=0.4, relwidth=1, relheight=0.6) + + products_label = ctk.CTkLabel( + bottom_layer, text="Products", font=("Helvetica", 16, "bold") + ) + products_label.pack(pady=10) + + products_frame = ctk.CTkScrollableFrame( + bottom_layer, fg_color="transparent", width=750, height=300 + ) + products_frame.pack(fill="both", expand=True, padx=10, pady=5) + + def create_product_card(parent, product_data): + card_width = 130 + card_height = 210 + card = ctk.CTkFrame( + parent, + corner_radius=5, + fg_color="#2b2b2b", + width=card_width, + height=card_height, + ) + card.pack_propagate(False) + + image_label = ctk.CTkLabel(card, text="") + image_label.pack(pady=5) + + product_images = product_data.get("images", []) + if product_images: + img_url = product_images[0].get("image_url") + if img_url: + fixed_url = fix_url(img_url) + try: + resp = requests.get(fixed_url) + if resp.status_code == 200: + pil_img = Image.open(io.BytesIO(resp.content)).resize((70, 70)) + tk_img = ImageTk.PhotoImage(pil_img) + image_label.configure(image=tk_img, text="") + image_label.image = tk_img + except Exception as e: + print(f"[DEBUG] Product image error: {e}") + + name_text = product_data.get("name", "No Name") + name_label = ctk.CTkLabel( + card, + text=name_text, + font=("Helvetica", 11, "bold"), + wraplength=120, + anchor="center", + justify="center", + ) + name_label.pack(padx=5, pady=3) + + price_val = product_data.get("price", 0.0) + price_label = ctk.CTkLabel( + card, + text=f"β« {price_val:,.1f}", + font=("Helvetica", 10, "bold"), + text_color="#ff4242", + ) + price_label.pack(side="bottom", pady=5) + + # Bind click event on card and its children. + def on_click(event, product=product_data): + switch_func("view_product", product) + + card.bind("<Button-1>", on_click) + return card + + def display_products(products, container): + for widget in container.winfo_children(): + widget.destroy() + + if not products: + ctk.CTkLabel(container, text="No products found.").pack(pady=10) + return + + grid_frame = ctk.CTkFrame(container, fg_color="transparent") + grid_frame.pack(fill="both", expand=True) + + columns = 5 # 5 products per row + for idx, product in enumerate(products): + row = idx // columns + col = idx % columns + product_card = create_product_card(grid_frame, product) + product_card.grid(row=row, column=col, padx=5, pady=5) + + # Display products + display_products(products, products_frame) + + return frame \ No newline at end of file diff --git a/app/frontend/components/shop/create_shop.py b/app/frontend/components/shop/create_shop.py index 7417248..6d5a991 100644 --- a/app/frontend/components/shop/create_shop.py +++ b/app/frontend/components/shop/create_shop.py @@ -1,93 +1,240 @@ import customtkinter as ctk -from tkinter import filedialog, messagebox import requests -import os +from PIL import Image, ImageTk +from tkinter import messagebox, filedialog +# import io + +SHOPPING = "#00c1ff" +DARK_BG = "#1f1f1f" +CARD_BG = "#2b2b2b" def create_shop_frame(parent, switch_func, API_URL, token): """ - Frame for a user to create a shop. - This version stores the token in the frame and provides an update_token method. + Create shop page with a modern dark theme and improved layout. """ - frame = ctk.CTkFrame(parent) - - # Store the initial token in the frame - frame.token = token - - # Define an update_token method so the global token can be propagated. - def update_token(new_token): - frame.token = new_token - frame.update_token = update_token - - title = ctk.CTkLabel(frame, text="Create Your Shop", font=("Helvetica", 18, "bold")) - title.pack(pady=10) - - button_label = ctk.CTkLabel(frame, text="Back to Dashboard", text_color="blue", cursor="hand2") - button_label.pack(pady=5) - button_label.bind("<Button-1>", lambda e: switch_func("dashboard", token)) - # Shop Name Field - shop_name_label = ctk.CTkLabel(frame, text="Shop Name:") - shop_name_label.pack(pady=5) - shop_name_entry = ctk.CTkEntry(frame, width=300) - shop_name_entry.pack(pady=5) - - # Address Field (using a default for demonstration) - address_label = ctk.CTkLabel(frame, text="Address:") - address_label.pack(pady=5) - address_entry = ctk.CTkEntry(frame, width=300) - address_entry.pack(pady=5) - - # Shop Image Upload - image_path = ctk.StringVar(value="No file selected") - def browse_image(): - filename = filedialog.askopenfilename( - title="Select Shop Image", - filetypes=[("Image Files", "*.png;*.jpg;*.jpeg;*.gif")] + # Main container frame + frame = ctk.CTkFrame(parent, fg_color=DARK_BG) + + # --- TOP BAR --- + top_bar = ctk.CTkFrame(frame, fg_color=SHOPPING, height=50) + top_bar.pack(fill="x", side="top") + + top_label = ctk.CTkLabel( + top_bar, + text="Create Shop", + text_color="white", + font=("Helvetica", 20, "bold") + ) + top_label.pack(side="left", padx=25) + + def go_back(): + switch_func("dashboard") + + back_button = ctk.CTkButton( + top_bar, + text="β Back", + fg_color="transparent", + text_color="white", + hover_color="#0096ff", + command=go_back, + width=80, + height=35, + font=("Helvetica", 14) + ) + back_button.pack(side="right", padx=20, pady=7) + + # --- MAIN SECTION: Sidebar + Content --- + main_section = ctk.CTkFrame(frame, fg_color="transparent") + main_section.pack(fill="both", expand=True) + + # LEFT SIDEBAR + sidebar_frame = ctk.CTkFrame(main_section, width=250, fg_color=CARD_BG, corner_radius=15) + sidebar_frame.pack(side="left", fill="y", padx=20, pady=20) + sidebar_frame.pack_propagate(False) + + sidebar_title = ctk.CTkLabel( + sidebar_frame, + text="Menu", + font=("Helvetica", 18, "bold"), + text_color=SHOPPING + ) + sidebar_title.pack(pady=(20, 10)) + + def create_nav_button(text, command=None, is_active=False): + return ctk.CTkButton( + sidebar_frame, + text=text, + fg_color="transparent", + text_color="white", + hover_color="#3b3b3b", + command=command, + height=40, + font=("Helvetica", 14), + state="disabled" if is_active else "normal" ) - if filename: - image_path.set(filename) - browse_button = ctk.CTkButton(frame, text="Browse Image", command=browse_image) - browse_button.pack(pady=5) - image_label = ctk.CTkLabel(frame, textvariable=image_path) - image_label.pack(pady=5) - - def submit_shop(): - # Use the token stored in the frame - current_token = frame.token - if not current_token: - messagebox.showerror("Error", "No access token found. Please log in again.") - return - name = shop_name_entry.get().strip() - address = address_entry.get().strip() - if not name or not address: - messagebox.showerror("Error", "Please fill all fields.") + nav_dashboard = create_nav_button("Dashboard", go_back) + nav_dashboard.pack(fill="x", padx=15, pady=5) + + def open_profile(): + switch_func("user_details") + + nav_profile = create_nav_button("My Profile", open_profile) + nav_profile.pack(fill="x", padx=15, pady=5) + + nav_orders = create_nav_button("My Orders", lambda: switch_func("user_orders")) + nav_orders.pack(fill="x", padx=15, pady=5) + + become_owner = create_nav_button("Become Shop Owner", is_active=True) + become_owner.pack(fill="x", padx=15, pady=5) + + # RIGHT CONTENT (Create Shop Form) + content_frame = ctk.CTkFrame(main_section, fg_color=CARD_BG, corner_radius=15) + content_frame.pack(side="left", fill="both", expand=True, padx=(0, 20), pady=20) + + # Header + header_frame = ctk.CTkFrame(content_frame, fg_color="transparent") + header_frame.pack(fill="x", padx=30, pady=20) + + title_label = ctk.CTkLabel( + header_frame, + text="Create Your Shop", + font=("Helvetica", 24, "bold"), + text_color=SHOPPING + ) + title_label.pack(anchor="w") + + subtitle_label = ctk.CTkLabel( + header_frame, + text="Fill in your shop details to get started", + font=("Helvetica", 14), + text_color="gray" + ) + subtitle_label.pack(anchor="w", pady=(5, 0)) + + # Main Content + main_content = ctk.CTkFrame(content_frame, fg_color="transparent") + main_content.pack(fill="both", expand=True, padx=30, pady=(0, 20)) + + # LEFT FORM + form_frame = ctk.CTkFrame(main_content, fg_color="transparent") + form_frame.pack(side="left", fill="both", expand=True, padx=(0, 20)) + + def create_form_field(label_text, placeholder_text): + field_frame = ctk.CTkFrame(form_frame, fg_color="transparent") + field_frame.pack(fill="x", pady=(0, 15)) + + label = ctk.CTkLabel( + field_frame, + text=label_text, + font=("Helvetica", 14), + text_color="white" + ) + label.pack(anchor="w") + + entry = ctk.CTkEntry( + field_frame, + height=40, + corner_radius=8, + placeholder_text=placeholder_text, + fg_color="#3b3b3b", + border_color=SHOPPING, + text_color="white" + ) + entry.pack(fill="x", pady=(5, 0)) + return entry + + shop_name_entry = create_form_field("Shop Name", "Enter your shop name...") + shop_description_entry = create_form_field("Shop Description", "Enter your shop description...") + shop_address_entry = create_form_field("Shop Address", "Enter your shop address...") + + # RIGHT PICTURE SECTION + pic_frame = ctk.CTkFrame(main_content, fg_color="transparent") + pic_frame.pack(side="left", fill="both", expand=True) + + pic_label = ctk.CTkLabel( + pic_frame, + text="Shop Logo", + font=("Helvetica", 18, "bold"), + text_color=SHOPPING + ) + pic_label.pack(anchor="w", pady=(0, 10)) + + photo_frame = ctk.CTkFrame(pic_frame, fg_color="#3b3b3b", corner_radius=10) + photo_frame.pack(fill="x", pady=(0, 20)) + + photo_label = ctk.CTkLabel( + photo_frame, + text="No image", + text_color="gray", + font=("Helvetica", 14) + ) + photo_label.pack(pady=20) + + def choose_photo(): + file_path = filedialog.askopenfilename( + title="Choose a Shop Logo", + filetypes=[("Image Files", "*.png *.jpg *.jpeg *.gif")], + ) + if file_path: + try: + pil_img = Image.open(file_path).resize((150, 150)) + tk_img = ImageTk.PhotoImage(pil_img) + photo_label.configure(image=tk_img, text="") + photo_label.image = tk_img + except Exception as e: + print(f"Image load error: {e}") + messagebox.showerror("Error", "Cannot load the image.") + + change_photo_button = ctk.CTkButton( + pic_frame, + text="Choose Image", + command=choose_photo, + corner_radius=8, + height=40, + font=("Helvetica", 14), + fg_color=SHOPPING, + hover_color="#0096ff", + ) + change_photo_button.pack(fill="x") + + # Create Shop Button + def create_shop(): + shop_name = shop_name_entry.get().strip() + shop_description = shop_description_entry.get().strip() + shop_address = shop_address_entry.get().strip() + + if not all([shop_name, shop_description, shop_address]): + messagebox.showwarning("Input Error", "All fields are required!") return - data = { - "name": name, - "address": address, - "description": "Default shop description" + headers = {"Authorization": f"Bearer {token}"} + payload = { + "name": shop_name, + "description": shop_description, + "address": shop_address, } - files = {} - if os.path.isfile(image_path.get()): - files["file"] = open(image_path.get(), "rb") - headers = {"Authorization": f"Bearer {current_token}"} - print("Creating shop with token:", current_token) + try: - resp = requests.post(f"{API_URL}/shops/create", data=data, files=files, headers=headers) + resp = requests.post(f"{API_URL}/shops/create", headers=headers, json=payload) if resp.status_code == 200: - shop = resp.json() messagebox.showinfo("Success", "Shop created successfully!") - switch_func("create_product", shop) # Pass the shop data to the view shop frame + switch_func("dashboard") else: - messagebox.showerror("Error", f"Failed to create shop. Status: {resp.status_code}\n{resp.text}") + messagebox.showerror("Error", "Failed to create shop.") except Exception as e: messagebox.showerror("Error", f"Request error: {e}") - finally: - if files: - files["file"].close() - submit_button = ctk.CTkButton(frame, text="Create Shop", command=submit_shop) - submit_button.pack(pady=10) + create_button = ctk.CTkButton( + form_frame, + text="Create Shop", + command=create_shop, + corner_radius=8, + height=45, + font=("Helvetica", 14, "bold"), + fg_color=SHOPPING, + hover_color="#0096ff", + ) + create_button.pack(fill="x", pady=(20, 0)) return frame diff --git a/app/frontend/components/shop/view_shop.py b/app/frontend/components/shop/view_shop.py index 8840001..2c951fa 100644 --- a/app/frontend/components/shop/view_shop.py +++ b/app/frontend/components/shop/view_shop.py @@ -4,72 +4,276 @@ from PIL import Image, ImageTk from tkinter import messagebox import io -def view_shop_frame(parent, switch_func, shop_data, token, API_URL): +SHOPPING = "#00c1ff" # Adding theme color +BACKEND_HOST = "http://127.0.0.1:8000" # Adjust if needed + + +def fix_url(url): + """ + Fix the URL to ensure it points to the correct static file location. + """ + if url.startswith("http"): + return url + # If the URL starts with "app/static/", remove that part + prefix = "app/static/" + if url.startswith(prefix): + url = url[len(prefix):] + return f"{BACKEND_HOST}/static/{url}" + + +def view_shop_frame(parent, switch_func, API_URL, token, shop_id): """ - Frame to display the ownerβs shop. - If shop_data is None, display a message that no shop has been created. - Otherwise, display the shop's details and its products. + CustomTkinter-based frame to display shop details and products. """ - frame = ctk.CTkFrame(parent) - - # If shop_data is None, show a message and a button to create a shop. - if shop_data is None: - msg = ctk.CTkLabel(frame, text="No shop created yet.", font=("Helvetica", 16, "bold")) - msg.pack(pady=20) - create_button = ctk.CTkButton(frame, text="Create Shop", command=lambda: switch_func("create_shop")) - create_button.pack(pady=10) - return frame - - # Otherwise, proceed normally. - shop_name = shop_data.get("name", "No Name") - shop_image_url = shop_data.get("image_url", "") - - # Header: Shop Image and Name - header_frame = ctk.CTkFrame(frame) - header_frame.pack(pady=10, fill="x") - - image_label = ctk.CTkLabel(header_frame, text="") - image_label.pack(side="left", padx=10) - try: - resp = requests.get(shop_image_url) - if resp.status_code == 200: - pil_img = Image.open(io.BytesIO(resp.content)).resize((80, 80)) - tk_img = ImageTk.PhotoImage(pil_img) - image_label.configure(image=tk_img, text="") - image_label.image = tk_img - except Exception as e: - print("Error loading shop image:", e) - name_label = ctk.CTkLabel(header_frame, text=shop_name, font=("Helvetica", 16, "bold")) - name_label.pack(side="left", padx=10) - - # Button to create a new product - def goto_create_product(): - switch_func("create_product", shop_data) - create_prod_button = ctk.CTkButton(frame, text="Add New Product", command=goto_create_product) - create_prod_button.pack(pady=10) - - # Products list - products_frame = ctk.CTkScrollableFrame(frame, width=750, height=300) - products_frame.pack(padx=10, pady=10, fill="both", expand=True) - - headers = {"Authorization": f"Bearer {token}"} - try: - resp = requests.get(f"{API_URL}/product/list?shop_id={shop_data.get('id')}", headers=headers) - if resp.status_code == 200: - products = resp.json() - if products: - for product in products: - prod_frame = ctk.CTkFrame(products_frame, fg_color="#2b2b2b", corner_radius=5) - prod_frame.pack(padx=5, pady=5, fill="x") - prod_name = product.get("name", "No Name") - prod_label = ctk.CTkLabel(prod_frame, text=prod_name, font=("Helvetica", 12, "bold")) - prod_label.pack(side="left", padx=10) - # Optionally, add more product details here + # Main frame + main_frame = ctk.CTkFrame(parent, fg_color="transparent") + main_frame.pack(fill="both", expand=True) + + # Create a container frame for content that can be refreshed + container_frame = ctk.CTkFrame(main_frame, fg_color="transparent") + container_frame.pack(fill="both", expand=True) + + def refresh_data(new_shop_id=None, new_token=None): + """Refresh the shop view with new data""" + nonlocal token, shop_id + if new_token: + token = new_token + if new_shop_id: + shop_id = new_shop_id + + # Clear the container + for widget in container_frame.winfo_children(): + widget.destroy() + + if not shop_id: + print("[DEBUG] No shop ID provided") + no_shop_label = ctk.CTkLabel( + container_frame, + text="No shop selected", + font=("Helvetica", 16) + ) + no_shop_label.pack(pady=20) + return + + # Back button at the top + back_button = ctk.CTkButton( + container_frame, + text="β Back to Dashboard", + command=lambda: switch_func("dashboard"), + fg_color=SHOPPING, + text_color="white", + width=150 + ) + back_button.pack(anchor="nw", padx=20, pady=10) + + # Create a scrollable frame for all content + content_frame = ctk.CTkScrollableFrame(container_frame, fg_color="transparent") + content_frame.pack(fill="both", expand=True, padx=20, pady=10) + + # Fetch shop details and products from the backend + headers = {"Authorization": f"Bearer {token}"} + try: + print(f"[DEBUG] Fetching shop data for ID: {shop_id}") + response = requests.get(f"{API_URL}/shops/get/{shop_id}", headers=headers) + print(f"[DEBUG] Response status: {response.status_code}") + + if response.status_code == 200: + data = response.json() + shop_data = data.get("shop", {}) + products = data.get("products", []) + print(f"[DEBUG] Shop data: {shop_data}") + print(f"[DEBUG] Number of products: {len(products)}") else: - ctk.CTkLabel(products_frame, text="No products found.").pack(pady=10) + print(f"[DEBUG] Error response: {response.text}") + messagebox.showerror("Error", f"Failed to fetch shop data. Status: {response.status_code}") + return + except Exception as e: + print(f"[DEBUG] Error fetching shop data: {e}") + messagebox.showerror("Error", f"Request error: {e}") + return + + # Shop Header Section + shop_header = ctk.CTkFrame(content_frame, fg_color="#2b2b2b", corner_radius=10) + shop_header.pack(fill="x", padx=10, pady=10) + + # Shop Image and Details in header + header_left = ctk.CTkFrame(shop_header, fg_color="transparent") + header_left.pack(side="left", padx=20, pady=20) + + shop_image_label = ctk.CTkLabel(header_left, text="") + shop_image_label.pack(pady=5) + + # Load shop image + shop_image_url = shop_data.get("image_url") + if shop_image_url: + fixed_url = fix_url(shop_image_url) + try: + print(f"[DEBUG] Loading shop image from: {fixed_url}") + resp = requests.get(fixed_url) + if resp.status_code == 200: + pil_img = Image.open(io.BytesIO(resp.content)).resize((150, 150)) + tk_img = ImageTk.PhotoImage(pil_img) + shop_image_label.configure(image=tk_img, text="") + shop_image_label.image = tk_img + else: + print(f"[DEBUG] Failed to load shop image. Status: {resp.status_code}") + except Exception as e: + print(f"[DEBUG] Shop image error: {e}") + + # Shop Details + header_right = ctk.CTkFrame(shop_header, fg_color="transparent") + header_right.pack(side="left", fill="both", expand=True, padx=20, pady=20) + + shop_name = ctk.CTkLabel( + header_right, + text=shop_data.get("name", "Shop Name"), + font=("Helvetica", 24, "bold"), + text_color=SHOPPING + ) + shop_name.pack(anchor="w") + + shop_description = ctk.CTkLabel( + header_right, + text=shop_data.get("description", "No description available."), + font=("Helvetica", 14), + wraplength=500, + justify="left" + ) + shop_description.pack(anchor="w", pady=10) + + shop_address = ctk.CTkLabel( + header_right, + text=f"π {shop_data.get('address', 'No address provided')}", + font=("Helvetica", 12), + justify="left" + ) + shop_address.pack(anchor="w") + + # Products Section + products_section = ctk.CTkFrame(content_frame, fg_color="transparent") + products_section.pack(fill="both", expand=True, padx=10, pady=10) + + # Products Header + products_header = ctk.CTkFrame(products_section, fg_color="transparent") + products_header.pack(fill="x", pady=10) + + products_title = ctk.CTkLabel( + products_header, + text="Products", + font=("Helvetica", 20, "bold"), + text_color=SHOPPING + ) + products_title.pack(side="left") + + products_count = ctk.CTkLabel( + products_header, + text=f"({len(products)} items)", + font=("Helvetica", 14) + ) + products_count.pack(side="left", padx=10) + + # Products Grid + products_grid = ctk.CTkFrame(products_section, fg_color="transparent") + products_grid.pack(fill="both", expand=True) + + def create_product_card(parent, product_data): + card = ctk.CTkFrame( + parent, + fg_color="#2b2b2b", + corner_radius=10, + width=200, + height=280 + ) + card.pack_propagate(False) + + # Product Image + image_frame = ctk.CTkFrame(card, fg_color="transparent", height=150) + image_frame.pack(fill="x", padx=10, pady=10) + image_frame.pack_propagate(False) + + image_label = ctk.CTkLabel(image_frame, text="") + image_label.pack(expand=True) + + # Load product image + product_images = product_data.get("images", []) + if product_images: + img_url = product_images[0].get("image_url") + if img_url: + fixed_url = fix_url(img_url) + try: + resp = requests.get(fixed_url) + if resp.status_code == 200: + pil_img = Image.open(io.BytesIO(resp.content)).resize((120, 120)) + tk_img = ImageTk.PhotoImage(pil_img) + image_label.configure(image=tk_img, text="") + image_label.image = tk_img + except Exception as e: + print(f"[DEBUG] Product image error: {e}") + + # Product Details + details_frame = ctk.CTkFrame(card, fg_color="transparent") + details_frame.pack(fill="both", expand=True, padx=10, pady=(0, 10)) + + name_label = ctk.CTkLabel( + details_frame, + text=product_data.get("name", "No Name"), + font=("Helvetica", 14, "bold"), + wraplength=180, + justify="left" + ) + name_label.pack(anchor="w") + + price_label = ctk.CTkLabel( + details_frame, + text=f"β« {product_data.get('price', 0.0):,.1f}", + font=("Helvetica", 14, "bold"), + text_color="#ff4242" + ) + price_label.pack(anchor="w", pady=5) + + # View Product Button + view_button = ctk.CTkButton( + details_frame, + text="View Details", + command=lambda p=product_data: switch_func("view_product", p), + fg_color=SHOPPING, + text_color="white", + height=30 + ) + view_button.pack(side="bottom", pady=5) + + return card + + if not products: + no_products_label = ctk.CTkLabel( + products_grid, + text="No products available in this shop.", + font=("Helvetica", 14), + text_color="gray" + ) + no_products_label.pack(pady=20) else: - messagebox.showerror("Error", f"Failed to fetch products. Status: {resp.status_code}") - except Exception as e: - messagebox.showerror("Error", f"Request error: {e}") + row = 0 + col = 0 + max_cols = 4 # Number of products per row + + for product in products: + product_card = create_product_card(products_grid, product) + product_card.grid(row=row, column=col, padx=10, pady=10, sticky="nsew") + + col += 1 + if col >= max_cols: + col = 0 + row += 1 + + # Configure grid columns to be equal width + for i in range(max_cols): + products_grid.grid_columnconfigure(i, weight=1) + + # Add the refresh_data method to the frame + main_frame.refresh_data = refresh_data + + # Initial load with empty shop_id + refresh_data() - return frame + return main_frame \ No newline at end of file diff --git a/app/frontend/components/user_details.py b/app/frontend/components/user_details.py index c22431a..7adf510 100644 --- a/app/frontend/components/user_details.py +++ b/app/frontend/components/user_details.py @@ -5,175 +5,175 @@ from tkinter import messagebox, filedialog import io SHOPPING = "#00c1ff" +DARK_BG = "#1f1f1f" +CARD_BG = "#2b2b2b" def user_details_frame(parent, switch_func, API_URL, token): """ - User details page with a "Become Shop Owner" button. - When clicked, it calls switch_func("create_shop") to navigate to shop creation. + User details page with a modern dark theme and improved layout. """ # Main container frame - frame = ctk.CTkFrame(parent, fg_color="transparent") + frame = ctk.CTkFrame(parent, fg_color=DARK_BG) # --- TOP BAR --- - top_bar = ctk.CTkFrame(frame, fg_color=SHOPPING, height=40) + top_bar = ctk.CTkFrame(frame, fg_color=SHOPPING, height=50) top_bar.pack(fill="x", side="top") top_label = ctk.CTkLabel( - top_bar, text="My Profile", text_color="white", font=("Helvetica", 16, "bold") + top_bar, + text="My Profile", + text_color="white", + font=("Helvetica", 20, "bold") ) - top_label.pack(side="left", padx=21) + top_label.pack(side="left", padx=25) def go_back(): switch_func("dashboard") back_button = ctk.CTkButton( top_bar, - text="Back", - fg_color="white", - text_color="black", + text="β Back", + fg_color="transparent", + text_color="white", + hover_color="#0096ff", command=go_back, - width=60, - height=30, + width=80, + height=35, + font=("Helvetica", 14) ) - back_button.pack(side="right", padx=20, pady=5) + back_button.pack(side="right", padx=20, pady=7) # --- MAIN SECTION: Sidebar + Content --- main_section = ctk.CTkFrame(frame, fg_color="transparent") main_section.pack(fill="both", expand=True) # LEFT SIDEBAR - sidebar_frame = ctk.CTkFrame(main_section, width=200, fg_color="#2b2b2b") - sidebar_frame.pack(side="left", fill="y") + sidebar_frame = ctk.CTkFrame(main_section, width=250, fg_color=CARD_BG, corner_radius=15) + sidebar_frame.pack(side="left", fill="y", padx=20, pady=20) + sidebar_frame.pack_propagate(False) + sidebar_title = ctk.CTkLabel( - sidebar_frame, text="Menu", font=("Helvetica", 14, "bold"), text_color="white" - ) - sidebar_title.pack(pady=(10, 5)) - nav_dashboard = ctk.CTkButton( - sidebar_frame, - text="Dashboard", - fg_color="#2b2b2b", - text_color="white", - hover_color="#3b3b3b", - command=go_back, - ) - nav_dashboard.pack(fill="x", padx=10, pady=5) - nav_profile = ctk.CTkButton( - sidebar_frame, - text="My Profile", - fg_color="#3b3b3b", - text_color="white", - hover_color="#3b3b3b", - state="disabled", + sidebar_frame, + text="Menu", + font=("Helvetica", 18, "bold"), + text_color=SHOPPING ) - nav_profile.pack(fill="x", padx=10, pady=5) - nav_orders = ctk.CTkButton( - sidebar_frame, - text="My Orders", - fg_color="#2b2b2b", - text_color="white", - hover_color="#3b3b3b", - command=lambda: switch_func("user_orders"), - ) - nav_orders.pack(fill="x", padx=10, pady=5) - # NEW: Become Shop Owner button - become_owner = ctk.CTkButton( - sidebar_frame, - text="Become Shop Owner", - fg_color="#2b2b2b", - text_color="white", - hover_color="#3b3b3b", - command=lambda: switch_func("create_shop"), - ) - become_owner.pack(fill="x", padx=10, pady=5) + sidebar_title.pack(pady=(20, 10)) + + def create_nav_button(text, command=None, is_active=False): + return ctk.CTkButton( + sidebar_frame, + text=text, + fg_color="transparent", + text_color="white", + hover_color="#3b3b3b", + command=command, + height=40, + font=("Helvetica", 14), + state="disabled" if is_active else "normal" + ) + + nav_dashboard = create_nav_button("Dashboard", go_back) + nav_dashboard.pack(fill="x", padx=15, pady=5) + + nav_profile = create_nav_button("My Profile", is_active=True) + nav_profile.pack(fill="x", padx=15, pady=5) + + nav_orders = create_nav_button("My Orders", lambda: switch_func("user_orders")) + nav_orders.pack(fill="x", padx=15, pady=5) + + become_owner = create_nav_button("Become Shop Owner", lambda: switch_func("create_shop")) + become_owner.pack(fill="x", padx=15, pady=5) # RIGHT CONTENT (User Details Form) - content_frame = ctk.CTkFrame(main_section, fg_color="transparent") - content_frame.pack(side="left", fill="both", expand=True, padx=20, pady=20) + content_frame = ctk.CTkFrame(main_section, fg_color=CARD_BG, corner_radius=15) + content_frame.pack(side="left", fill="both", expand=True, padx=(0, 20), pady=20) + + # Header + header_frame = ctk.CTkFrame(content_frame, fg_color="transparent") + header_frame.pack(fill="x", padx=30, pady=20) + title_label = ctk.CTkLabel( - content_frame, - text="My Profile", - font=("Helvetica", 18, "bold"), - text_color="white", + header_frame, + text="Profile Information", + font=("Helvetica", 24, "bold"), + text_color=SHOPPING ) - title_label.pack(anchor="w", pady=(0, 5)) + title_label.pack(anchor="w") + subtitle_label = ctk.CTkLabel( - content_frame, + header_frame, text="Manage your profile information to keep your account secure", - font=("Helvetica", 12), - text_color="#cccccc", + font=("Helvetica", 14), + text_color="gray" ) - subtitle_label.pack(anchor="w", pady=(0, 15)) + subtitle_label.pack(anchor="w", pady=(5, 0)) - right_main = ctk.CTkFrame(content_frame, fg_color="transparent") - right_main.pack(fill="both", expand=True) + # Main Content + main_content = ctk.CTkFrame(content_frame, fg_color="transparent") + main_content.pack(fill="both", expand=True, padx=30, pady=(0, 20)) # LEFT FORM - form_frame = ctk.CTkFrame(right_main, fg_color="transparent") + form_frame = ctk.CTkFrame(main_content, fg_color="transparent") form_frame.pack(side="left", fill="both", expand=True, padx=(0, 20)) - # Username - username_label = ctk.CTkLabel( - form_frame, text="Username", font=("Helvetica", 12), text_color="white" - ) - username_label.pack(anchor="w") - username_entry = ctk.CTkEntry(form_frame, placeholder_text="Enter your username...") - username_entry.pack(anchor="w", pady=(0, 10)) - # Name - name_label = ctk.CTkLabel( - form_frame, text="Name", font=("Helvetica", 12), text_color="white" - ) - name_label.pack(anchor="w") - name_entry = ctk.CTkEntry(form_frame, placeholder_text="Enter your name...") - name_entry.pack(anchor="w", pady=(0, 10)) - # Email - email_label = ctk.CTkLabel( - form_frame, text="Email", font=("Helvetica", 12), text_color="white" - ) - email_label.pack(anchor="w") - email_entry = ctk.CTkEntry(form_frame, placeholder_text="Enter your email...") - email_entry.pack(anchor="w", pady=(0, 10)) - # Phone - phone_label = ctk.CTkLabel( - form_frame, text="Phone", font=("Helvetica", 12), text_color="white" - ) - phone_label.pack(anchor="w") - phone_entry = ctk.CTkEntry(form_frame, placeholder_text="Enter your phone...") - phone_entry.pack(anchor="w", pady=(0, 10)) - # Gender - gender_label = ctk.CTkLabel( - form_frame, text="Gender", font=("Helvetica", 12), text_color="white" - ) - gender_label.pack(anchor="w", pady=(10, 0)) - gender_var = ctk.StringVar(value="Male") + + def create_form_field(label_text, placeholder_text): + field_frame = ctk.CTkFrame(form_frame, fg_color="transparent") + field_frame.pack(fill="x", pady=(0, 15)) + + label = ctk.CTkLabel( + field_frame, + text=label_text, + font=("Helvetica", 14), + text_color="white" + ) + label.pack(anchor="w") + + entry = ctk.CTkEntry( + field_frame, + height=40, + corner_radius=8, + placeholder_text=placeholder_text, + fg_color="#3b3b3b", + border_color=SHOPPING, + text_color="white" + ) + entry.pack(fill="x", pady=(5, 0)) + return entry + + username_entry = create_form_field("Username", "Enter your username...") + name_entry = create_form_field("Name", "Enter your name...") + email_entry = create_form_field("Email", "Enter your email...") + phone_entry = create_form_field("Phone", "Enter your phone number...") + + # Gender Selection gender_frame = ctk.CTkFrame(form_frame, fg_color="transparent") - gender_frame.pack(anchor="w", pady=5) - male_radio = ctk.CTkRadioButton( - gender_frame, text="Male", variable=gender_var, value="Male", text_color="white" - ) - female_radio = ctk.CTkRadioButton( - gender_frame, - text="Female", - variable=gender_var, - value="Female", - text_color="white", - ) - other_radio = ctk.CTkRadioButton( + gender_frame.pack(fill="x", pady=(0, 15)) + + ctk.CTkLabel( gender_frame, - text="Other", - variable=gender_var, - value="Other", - text_color="white", - ) - male_radio.pack(side="left", padx=5) - female_radio.pack(side="left", padx=5) - other_radio.pack(side="left", padx=5) - # Birthday - birthday_label = ctk.CTkLabel( - form_frame, text="Date of Birth", font=("Helvetica", 12), text_color="white" - ) - birthday_label.pack(anchor="w", pady=(10, 0)) - birthday_entry = ctk.CTkEntry(form_frame, placeholder_text="dd/mm/yyyy") - birthday_entry.pack(anchor="w", pady=(0, 10)) + text="Gender", + font=("Helvetica", 14), + text_color="white" + ).pack(anchor="w") + + gender_var = ctk.StringVar(value="Male") + gender_options = ctk.CTkFrame(gender_frame, fg_color="transparent") + gender_options.pack(fill="x", pady=(5, 0)) + + for gender in ["Male", "Female", "Other"]: + ctk.CTkRadioButton( + gender_options, + text=gender, + variable=gender_var, + value=gender, + text_color="white", + fg_color=SHOPPING, + hover_color="#0096ff" + ).pack(side="left", padx=(0, 20)) + + birthday_entry = create_form_field("Date of Birth", "dd/mm/yyyy") # Save Button def save_profile(): @@ -197,26 +197,38 @@ def user_details_frame(parent, switch_func, API_URL, token): save_button = ctk.CTkButton( form_frame, - text="Save", - fg_color=SHOPPING, - text_color="white", + text="Save Changes", command=save_profile, - width=80, + corner_radius=8, + height=45, + font=("Helvetica", 14, "bold"), + fg_color=SHOPPING, + hover_color="#0096ff", ) - save_button.pack(anchor="w", pady=(20, 10)) + save_button.pack(fill="x", pady=(20, 0)) # RIGHT PICTURE SECTION - pic_frame = ctk.CTkFrame(right_main, fg_color="transparent") + pic_frame = ctk.CTkFrame(main_content, fg_color="transparent") pic_frame.pack(side="left", fill="both", expand=True) + pic_label = ctk.CTkLabel( pic_frame, text="Profile Picture", - font=("Helvetica", 12, "bold"), - text_color="white", + font=("Helvetica", 18, "bold"), + text_color=SHOPPING ) pic_label.pack(anchor="w", pady=(0, 10)) - photo_label = ctk.CTkLabel(pic_frame, text="No image", text_color="white") - photo_label.pack(anchor="w", pady=(0, 10)) + + photo_frame = ctk.CTkFrame(pic_frame, fg_color="#3b3b3b", corner_radius=10) + photo_frame.pack(fill="x", pady=(0, 20)) + + photo_label = ctk.CTkLabel( + photo_frame, + text="No image", + text_color="gray", + font=("Helvetica", 14) + ) + photo_label.pack(pady=20) def choose_photo(): file_path = filedialog.askopenfilename( @@ -225,7 +237,7 @@ def user_details_frame(parent, switch_func, API_URL, token): ) if file_path: try: - pil_img = Image.open(file_path).resize((100, 100)) + pil_img = Image.open(file_path).resize((150, 150)) tk_img = ImageTk.PhotoImage(pil_img) photo_label.configure(image=tk_img, text="") photo_label.image = tk_img @@ -236,12 +248,14 @@ def user_details_frame(parent, switch_func, API_URL, token): change_photo_button = ctk.CTkButton( pic_frame, text="Choose Image", - fg_color="white", - text_color="black", command=choose_photo, - width=80, + corner_radius=8, + height=40, + font=("Helvetica", 14), + fg_color=SHOPPING, + hover_color="#0096ff", ) - change_photo_button.pack(anchor="w") + change_photo_button.pack(fill="x") def fetch_user_info(): headers = {"Authorization": f"Bearer {token}"} @@ -265,9 +279,7 @@ def user_details_frame(parent, switch_func, API_URL, token): try: resp_pic = requests.get(pic_url) if resp_pic.status_code == 200: - pil_img = Image.open(io.BytesIO(resp_pic.content)).resize( - (100, 100) - ) + pil_img = Image.open(io.BytesIO(resp_pic.content)).resize((150, 150)) tk_img = ImageTk.PhotoImage(pil_img) photo_label.configure(image=tk_img, text="") photo_label.image = tk_img diff --git a/app/frontend/components/user_orders.py b/app/frontend/components/user_orders.py index 6d72df8..cbb83ea 100644 --- a/app/frontend/components/user_orders.py +++ b/app/frontend/components/user_orders.py @@ -5,111 +5,220 @@ from tkinter import messagebox import io SHOPPING = "#00c1ff" +DARK_BG = "#1f1f1f" +CARD_BG = "#2b2b2b" def user_orders_frame(parent, switch_func, API_URL, token): """ - A two-column user orders page that displays the products the user has purchased - along with associated shop details. The layout and color scheme match your dashboard. + A modern user orders page that displays the products the user has purchased + along with associated shop details. """ - - # Main container frame with transparent background - frame = ctk.CTkFrame(parent, fg_color="transparent") + # Main container frame with dark background + frame = ctk.CTkFrame(parent, fg_color=DARK_BG) # ----------------- TOP BAR ----------------- - top_bar = ctk.CTkFrame(frame, fg_color=SHOPPING, height=40) + top_bar = ctk.CTkFrame(frame, fg_color=SHOPPING, height=50) top_bar.pack(fill="x", side="top") top_label = ctk.CTkLabel( - top_bar, text="My Orders", text_color="white", font=("Helvetica", 16, "bold") + top_bar, + text="My Orders", + text_color="white", + font=("Helvetica", 20, "bold") ) - top_label.pack(side="left", padx=20) + top_label.pack(side="left", padx=25) def go_back(): switch_func("dashboard") back_button = ctk.CTkButton( top_bar, - text="Back", - fg_color="white", - text_color="black", + text="β Back", + fg_color="transparent", + text_color="white", + hover_color="#0096ff", command=go_back, - width=60, - height=30, + width=80, + height=35, + font=("Helvetica", 14) ) - back_button.pack(side="right", padx=20, pady=5) + back_button.pack(side="right", padx=20, pady=7) # ----------------- MAIN SECTION (Sidebar + Content) ----------------- main_section = ctk.CTkFrame(frame, fg_color="transparent") main_section.pack(fill="both", expand=True) # ----------------- LEFT SIDEBAR ----------------- - sidebar_frame = ctk.CTkFrame(main_section, width=200, fg_color="#2b2b2b") - sidebar_frame.pack(side="left", fill="y") + sidebar_frame = ctk.CTkFrame(main_section, width=250, fg_color=CARD_BG, corner_radius=15) + sidebar_frame.pack(side="left", fill="y", padx=20, pady=20) + sidebar_frame.pack_propagate(False) sidebar_title = ctk.CTkLabel( - sidebar_frame, text="Menu", font=("Helvetica", 14, "bold"), text_color="white" + sidebar_frame, + text="Menu", + font=("Helvetica", 18, "bold"), + text_color=SHOPPING ) - sidebar_title.pack(pady=(10, 5)) + sidebar_title.pack(pady=(20, 10)) + + def create_nav_button(text, command=None, is_active=False): + return ctk.CTkButton( + sidebar_frame, + text=text, + fg_color="transparent", + text_color="white", + hover_color="#3b3b3b", + command=command, + height=40, + font=("Helvetica", 14), + state="disabled" if is_active else "normal" + ) + + nav_dashboard = create_nav_button("Dashboard", go_back) + nav_dashboard.pack(fill="x", padx=15, pady=5) def open_profile(): switch_func("user_details") - nav_dashboard = ctk.CTkButton( - sidebar_frame, - text="Dashboard", - fg_color="#2b2b2b", - text_color="white", - hover_color="#3b3b3b", - command=go_back, - ) - nav_dashboard.pack(fill="x", padx=10, pady=5) + nav_profile = create_nav_button("My Profile", open_profile) + nav_profile.pack(fill="x", padx=15, pady=5) - nav_profile = ctk.CTkButton( - sidebar_frame, - text="My Profile", - fg_color="#2b2b2b", - text_color="white", - hover_color="#3b3b3b", - command=open_profile, - ) - nav_profile.pack(fill="x", padx=10, pady=5) - - nav_orders = ctk.CTkButton( - sidebar_frame, - text="My Orders", - fg_color="#3b3b3b", # Active/selected state - text_color="white", - hover_color="#3b3b3b", - state="disabled", - ) - nav_orders.pack(fill="x", padx=10, pady=5) + nav_orders = create_nav_button("My Orders", is_active=True) + nav_orders.pack(fill="x", padx=15, pady=5) # ----------------- RIGHT CONTENT (Orders List) ----------------- - content_frame = ctk.CTkFrame(main_section, fg_color="transparent") - content_frame.pack(side="left", fill="both", expand=True, padx=20, pady=20) + content_frame = ctk.CTkFrame(main_section, fg_color=CARD_BG, corner_radius=15) + content_frame.pack(side="left", fill="both", expand=True, padx=(0, 20), pady=20) + + # Header + header_frame = ctk.CTkFrame(content_frame, fg_color="transparent") + header_frame.pack(fill="x", padx=30, pady=20) main_title_label = ctk.CTkLabel( - content_frame, - text="Your Orders", - font=("Helvetica", 18, "bold"), - text_color="white", + header_frame, + text="Order History", + font=("Helvetica", 24, "bold"), + text_color=SHOPPING ) - main_title_label.pack(anchor="w", pady=(0, 5)) + main_title_label.pack(anchor="w") subtitle_label = ctk.CTkLabel( - content_frame, - text="Review the products you have purchased.", - font=("Helvetica", 12), - text_color="#cccccc", + header_frame, + text="Review the products you have purchased", + font=("Helvetica", 14), + text_color="gray" + ) + subtitle_label.pack(anchor="w", pady=(5, 0)) + + # Orders List Container + orders_list_frame = ctk.CTkScrollableFrame( + content_frame, + fg_color="transparent", + corner_radius=0 ) - subtitle_label.pack(anchor="w", pady=(0, 15)) + orders_list_frame.pack(fill="both", expand=True, padx=30, pady=(0, 20)) + + def create_order_card(order_data): + product = order_data.get("product", {}) + order_date = order_data.get("order_date", "Unknown Date") + + card = ctk.CTkFrame(orders_list_frame, fg_color="#3b3b3b", corner_radius=10) + card.pack(fill="x", pady=5) + + # Left: Product image + image_frame = ctk.CTkFrame(card, fg_color="transparent", width=100, height=100) + image_frame.pack(side="left", padx=15, pady=15) + image_frame.pack_propagate(False) + + image_label = ctk.CTkLabel(image_frame, text="") + image_label.pack(expand=True) + + if product.get("images"): + try: + img_url = product["images"][0]["image_url"] + img_resp = requests.get(img_url) + if img_resp.status_code == 200: + pil_img = Image.open(io.BytesIO(img_resp.content)).resize((80, 80)) + tk_img = ImageTk.PhotoImage(pil_img) + image_label.configure(image=tk_img, text="") + image_label.image = tk_img + except Exception as ex: + print(f"Product image error: {ex}") + + # Right: Order details + info_frame = ctk.CTkFrame(card, fg_color="transparent") + info_frame.pack(side="left", fill="both", expand=True, padx=15, pady=15) + + # Product Name + ctk.CTkLabel( + info_frame, + text=product.get("name", "No Name"), + font=("Helvetica", 16, "bold"), + text_color="white" + ).pack(anchor="w") + + # Price and Date + price = product.get("price", 0.0) + ctk.CTkLabel( + info_frame, + text=f"β« {price:,.1f}", + font=("Helvetica", 14), + text_color=SHOPPING + ).pack(anchor="w", pady=(5, 0)) + + ctk.CTkLabel( + info_frame, + text=f"Ordered on: {order_date}", + font=("Helvetica", 12), + text_color="gray" + ).pack(anchor="w", pady=(5, 0)) + + # Shop Name + shop_id = product.get("shop_id") + shop_name_label = ctk.CTkLabel( + info_frame, + text="Shop: Loading...", + font=("Helvetica", 12), + text_color="gray" + ) + shop_name_label.pack(anchor="w", pady=(5, 0)) + + def fetch_shop_and_update_label(sid, label_widget): + headers = {"Authorization": f"Bearer {token}"} + try: + sresp = requests.get(f"{API_URL}/shop/get/{sid}", headers=headers) + if sresp.status_code == 200: + shop_data = sresp.json() + label_widget.configure( + text=f"Shop: {shop_data.get('name', 'No Shop Name')}" + ) + else: + label_widget.configure(text="Shop: Not found") + except Exception: + label_widget.configure(text="Shop: Error fetching") + + fetch_shop_and_update_label(shop_id, shop_name_label) + + # View Order Button + def view_order(): + messagebox.showinfo( + "Order Details", + f"View details for order of {product.get('name')}" + ) - # A frame to hold the list of orders - orders_list_frame = ctk.CTkFrame(content_frame, fg_color="transparent") - orders_list_frame.pack(fill="both", expand=True) + view_button = ctk.CTkButton( + info_frame, + text="View Order", + command=view_order, + corner_radius=8, + height=35, + font=("Helvetica", 12), + fg_color=SHOPPING, + hover_color="#0096ff", + ) + view_button.pack(anchor="e", pady=(10, 0)) - # ----------- Functions to fetch and display orders ----------- def fetch_orders(): """ Fetch the list of user orders from the API. @@ -118,110 +227,46 @@ def user_orders_frame(parent, switch_func, API_URL, token): try: resp = requests.get(f"{API_URL}/orders/list", headers=headers) if resp.status_code == 200: - orders = resp.json() # Expect a list of order dicts - display_orders(orders, orders_list_frame) + orders = resp.json() + display_orders(orders) else: messagebox.showerror("Error", "Failed to fetch orders.") except Exception as ex: messagebox.showerror("Error", f"Request error: {ex}") - def display_orders(orders, container): + def display_orders(orders): """ Display each order with product and shop details. """ # Clear previous content - for widget in container.winfo_children(): + for widget in orders_list_frame.winfo_children(): widget.destroy() if not orders: - ctk.CTkLabel(container, text="No orders found.", text_color="white").pack( - pady=10 - ) - return + no_orders_frame = ctk.CTkFrame(orders_list_frame, fg_color="transparent") + no_orders_frame.pack(expand=True, pady=20) - for order in orders: - # Assume each order dict includes a 'product' dict and an 'order_date' - product = order.get("product", {}) - order_date = order.get("order_date", "Unknown Date") - - order_frame = ctk.CTkFrame(container, corner_radius=5, fg_color="#2b2b2b") - order_frame.pack(fill="x", padx=5, pady=5) - - # Left: Product image - image_label = ctk.CTkLabel(order_frame, text="") - image_label.pack(side="left", padx=5, pady=5) - if product.get("images"): - try: - img_url = product["images"][0]["image_url"] - img_resp = requests.get(img_url) - if img_resp.status_code == 200: - pil_img = Image.open(io.BytesIO(img_resp.content)).resize( - (60, 60) - ) - tk_img = ImageTk.PhotoImage(pil_img) - image_label.configure(image=tk_img, text="") - image_label.image = tk_img - except Exception as ex: - print(f"Product image error: {ex}") - - # Right: Order details and shop info - info_frame = ctk.CTkFrame(order_frame, fg_color="transparent") - info_frame.pack(side="left", fill="both", expand=True, padx=10) - - # Product Name - ctk.CTkLabel( - info_frame, - text=product.get("name", "No Name"), - font=("Helvetica", 13, "bold"), - text_color="white", - ).pack(anchor="w") - - # Price (and order date) - price = product.get("price", 0.0) - ctk.CTkLabel( - info_frame, text=f"Price: {price:.2f}", text_color="#cccccc" - ).pack(anchor="w") ctk.CTkLabel( - info_frame, text=f"Ordered on: {order_date}", text_color="#cccccc" - ).pack(anchor="w") - - # Shop Name - shop_id = product.get("shop_id") - shop_name_label = ctk.CTkLabel( - info_frame, text="Shop: Loading...", text_color="#cccccc" - ) - shop_name_label.pack(anchor="w") - - def fetch_shop_and_update_label(sid, label_widget): - headers = {"Authorization": f"Bearer {token}"} - try: - sresp = requests.get(f"{API_URL}/shop/get/{sid}", headers=headers) - if sresp.status_code == 200: - shop_data = sresp.json() - label_widget.configure( - text=f"Shop: {shop_data.get('name', 'No Shop Name')}" - ) - else: - label_widget.configure(text="Shop: Not found") - except Exception: - label_widget.configure(text="Shop: Error fetching") - - fetch_shop_and_update_label(shop_id, shop_name_label) - - # "View Order" button (placeholder action) - def view_order(): - messagebox.showinfo( - "Order Details", f"View details for order of {product.get('name')}" - ) - - view_button = ctk.CTkButton( - info_frame, - text="View Order", + no_orders_frame, + text="No orders found", + font=("Helvetica", 16), + text_color="gray" + ).pack() + + ctk.CTkButton( + no_orders_frame, + text="Start Shopping", + command=go_back, + corner_radius=8, + height=40, + font=("Helvetica", 14), fg_color=SHOPPING, - text_color="white", - command=view_order, - ) - view_button.pack(anchor="e", pady=(5, 0)) + hover_color="#0096ff", + ).pack(pady=(20, 0)) + return + + for order in orders: + create_order_card(order) # Fetch orders when the frame loads fetch_orders() diff --git a/app/frontend/main.py b/app/frontend/main.py index 145c151..e53b707 100644 --- a/app/frontend/main.py +++ b/app/frontend/main.py @@ -48,8 +48,8 @@ register = register_frame(root, switch_frame, API_URL) create_shop = create_shop_frame( root, switch_frame, API_URL, access_token ) # Accepts token -# Pass a placeholder (None) for shop_data in view_shop_frame -view_shop = view_shop_frame(root, switch_frame, None, access_token, API_URL) +# Fix the parameter order for view_shop_frame +view_shop = view_shop_frame(root, switch_frame, API_URL, access_token, None) # Pass shop_id as None initially product = create_product_frame(root, switch_frame, API_URL, access_token) category = category_frame(root, switch_frame, API_URL, access_token) dashboard = dashboard_frame(root, switch_frame, API_URL, access_token) -- GitLab