diff --git a/app/backend/routes/product.py b/app/backend/routes/product.py index 4ccdeb6cb7937622a51f0ffa7ffd07c9293bd946..daaaf9e04e839ec2e81e611462bff2ff57f29973 100644 --- a/app/backend/routes/product.py +++ b/app/backend/routes/product.py @@ -55,6 +55,12 @@ def create_product( return product +@router.get("/list", response_model=list[ProductRead]) +def read_all_products(session: Session = Depends(get_session)): + products = session.query(Product).all() + return products + + @router.get("/{product_id}", response_model=ProductRead) def read_product(product_id: int, session: Session = Depends(get_session)): product = session.get(Product, product_id) diff --git a/app/backend/routes/shop.py b/app/backend/routes/shop.py index ffea6d91b86b52a89864fbf089686081e929589d..6641eb36a99fef3eeb2a3580430a4d4dbe8d951f 100644 --- a/app/backend/routes/shop.py +++ b/app/backend/routes/shop.py @@ -19,12 +19,12 @@ def create_shop( description: str = Form(None), file: UploadFile = File(None), session: Session = Depends(get_session), - # current_user: User = Depends(get_current_user), - owner_id = int, + current_user: User = Depends(get_current_user), + #owner_id = int, ): - # shop = ShopCreate(name=name, description=description, owner_id=current_user.id) - shop = ShopCreate(name=name, description=description, owner_id=owner_id) + shop = ShopCreate(name=name, description=description, owner_id=current_user.id) + #shop = ShopCreate(name=name, description=description, owner_id=owner_id) db_shop = Shop.from_orm(shop) if file and file.filename: @@ -42,12 +42,18 @@ def create_shop( session.commit() session.refresh(db_shop) - if file: - # Delete the image file after session commit - os.remove(file_location) + # if file: + # # Delete the image file after session commit + # os.remove(file_location) return db_shop +@router.get("/list", response_model=list[ShopRead]) +def get_all_shops(session: Session = Depends(get_session)): + shops = session.query(Shop).all() + return shops + + @router.get("/{shop_id}", response_model=ShopRead) def read_shop(shop_id: int, session: Session = Depends(get_session)): shop = session.get(Shop, shop_id) diff --git a/app/frontend/components/auth/login.py b/app/frontend/components/auth/login.py index 64a9f81c32cf23e640fc4999041a6574144781a6..1c7baeb1a6c9a3502caa178c898b74150f19f54f 100644 --- a/app/frontend/components/auth/login.py +++ b/app/frontend/components/auth/login.py @@ -23,7 +23,8 @@ def login_frame(parent, switch_func, API_URL): if response.status_code == 200: access_token = response_data["access_token"] messagebox.showinfo("Login Successful", f"Welcome back, {email}!") - switch_func("create_shop", access_token) + switch_func("dashboard", access_token) + print(f"Access Token in login: {access_token}") # Debugging line else: messagebox.showerror( "Login Failed", response_data.get("detail", "Invalid credentials") diff --git a/app/frontend/components/dashboard.py b/app/frontend/components/dashboard.py new file mode 100644 index 0000000000000000000000000000000000000000..32a128a096bba1ff38b15fd47f128b03212bfadc --- /dev/null +++ b/app/frontend/components/dashboard.py @@ -0,0 +1,205 @@ +import customtkinter as ctk +import requests +from PIL import Image, ImageTk +from tkinter import messagebox +import io + +SHOPPING = "#00c1ff" + +def dashboard_frame(parent, switch_func, API_URL, token): + + # Main container frame + 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", + text_color="white", + font=("Helvetica", 20, "bold") + ) + logo_label.place(relx=0.01, rely=0.1, relwidth=0.15, relheight=0.8) + + # Search bar (Center) + search_entry = ctk.CTkEntry( + header_frame, + placeholder_text="Search in Shop...", + height=30 + ) + search_entry.place(relx=0.25, rely=0.25, relwidth=0.45, relheight=0.5) + + def perform_search(): + """Call an endpoint to search products by keyword.""" + keyword = search_entry.get().strip() + if not keyword: + messagebox.showinfo("Info", "Please enter a search keyword.") + return + try: + headers = {"Authorization": f"Bearer {token}"} + resp = requests.get(f"{API_URL}/product?search={keyword}", headers=headers) + if resp.status_code == 200: + products = resp.json() + display_products(products, bottom_products_frame) + else: + messagebox.showerror("Error", "Failed to fetch search results.") + except Exception as e: + messagebox.showerror("Error", f"Request error: {e}") + + # Search button + search_button = ctk.CTkButton( + header_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) + + # ------------- 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) + + # Section Title + featured_label = ctk.CTkLabel( + middle_frame, + text="TOP SHOP", + font=("Helvetica", 16, "bold") + ) + featured_label.pack(pady=5) + + # A frame to hold the featured shops + top_shops_frame = ctk.CTkFrame(middle_frame, fg_color="transparent") + top_shops_frame.pack(fill="both", expand=True, padx=10, pady=5) + + def display_shops(shops, container): + """Given a list of shop dicts, display them in the given container.""" + # Clear old widgets + for widget in container.winfo_children(): + widget.destroy() + + if not shops: + ctk.CTkLabel(container, text="No shops found.").pack(pady=10) + return + + # Display shops in a vertical list + for shop in shops: + sframe = ctk.CTkFrame(container, corner_radius=5, fg_color="#2b2b2b") + sframe.pack(fill="x", padx=5, pady=5) + + # Shop image/logo on left + image_label = ctk.CTkLabel(sframe, text="") + image_label.pack(side="left", padx=5, pady=5) + + # Load shop logo if available (assumes key "logo_url") + logo_url = shop.get("logo_url") + if logo_url: + try: + resp = requests.get(logo_url) + if resp.status_code == 200: + pil_img = Image.open(io.BytesIO(resp.content)).resize((50, 50)) + tk_img = ImageTk.PhotoImage(pil_img) + image_label.configure(image=tk_img, text="") + image_label.image = tk_img + except Exception as e: + print(f"Shop image error: {e}") + + # Shop info on right + info_frame = ctk.CTkFrame(sframe, fg_color="transparent") + info_frame.pack(side="left", fill="both", expand=True, padx=10) + + ctk.CTkLabel(info_frame, text=shop.get("name", "No Name"), font=("Helvetica", 13, "bold")).pack(anchor="w") + # Optionally add more shop details, for example a rating if available: + if shop.get("rating"): + ctk.CTkLabel(info_frame, text=f"Rating: {shop['rating']}").pack(anchor="w") + + # ------------- BOTTOM (Recommendations - Products) ------------- + bottom_frame = ctk.CTkFrame(frame, fg_color="transparent") + bottom_frame.place(relx=0, rely=0.35, relwidth=1, relheight=0.65) + + # Section Title + recommend_label = ctk.CTkLabel( + bottom_frame, + text="TODAY'S RECOMMENDATIONS", + font=("Helvetica", 16, "bold") + ) + recommend_label.pack(pady=5) + + bottom_products_frame = ctk.CTkFrame(bottom_frame, fg_color="transparent") + bottom_products_frame.pack(fill="both", expand=True, padx=10, pady=5) + + def display_products(products, container): + """Given a list of product dicts, display them in the given container.""" + # Clear old widgets + for widget in container.winfo_children(): + widget.destroy() + + if not products: + ctk.CTkLabel(container, text="No products found.").pack(pady=10) + return + + # Display products in a vertical list + for product in products: + pframe = ctk.CTkFrame(container, corner_radius=5, fg_color="#2b2b2b") + pframe.pack(fill="x", padx=5, pady=5) + + # Product image on left + image_label = ctk.CTkLabel(pframe, text="") + image_label.pack(side="left", padx=5, pady=5) + + # Load first image if available + if product.get("images"): + try: + img_url = product["images"][0]["image_url"] + resp = requests.get(img_url) + if resp.status_code == 200: + pil_img = Image.open(io.BytesIO(resp.content)).resize((50, 50)) + tk_img = ImageTk.PhotoImage(pil_img) + image_label.configure(image=tk_img, text="") + image_label.image = tk_img + except Exception as e: + print(f"Product image error: {e}") + + # Product info on right + info_frame = ctk.CTkFrame(pframe, fg_color="transparent") + info_frame.pack(side="left", fill="both", expand=True, padx=10) + + ctk.CTkLabel(info_frame, text=product.get("name", "No Name"), font=("Helvetica", 13, "bold")).pack(anchor="w") + price = product.get("price", 0.0) + ctk.CTkLabel(info_frame, text=f"Price: {price:.2f}").pack(anchor="w") + + def fetch_featured_shops(): + """Fetch some 'featured' or 'top' shops for the middle section.""" + headers = {"Authorization": f"Bearer {token}"} + try: + resp = requests.get(f"{API_URL}/shops/list", headers=headers) + if resp.status_code == 200: + shops = resp.json() + # Slice the list to display only a handful of top shops + display_shops(shops[:5], top_shops_frame) + else: + messagebox.showerror("Error", "Failed to fetch featured shops.") + except Exception as e: + messagebox.showerror("Error", f"Request error: {e}") + + def fetch_recommendations(): + """Fetch recommended products for the bottom section.""" + headers = {"Authorization": f"Bearer {token}"} + try: + resp = requests.get(f"{API_URL}/product/list", headers=headers) + if resp.status_code == 200: + products = resp.json() + display_products(products, bottom_products_frame) + else: + messagebox.showerror("Error", "Failed to fetch recommended products.") + except Exception as e: + messagebox.showerror("Error", f"Request error: {e}") + + # Load data on start + fetch_featured_shops() + fetch_recommendations() + + return frame diff --git a/app/frontend/components/shop/create_shop.py b/app/frontend/components/shop/create_shop.py index 5319ebe790fe8ce412bb9eb9b5f25fa5f89f4a71..44d4d79a66ee3cdb39d263915ce81b2131ae50af 100644 --- a/app/frontend/components/shop/create_shop.py +++ b/app/frontend/components/shop/create_shop.py @@ -3,9 +3,14 @@ from tkinter import messagebox, filedialog import requests import os - def create_shop_frame(parent, switch_func, API_URL, token): frame = ctk.CTkFrame(parent) + frame.access_token = token + + def update_token(new_token): + frame.access_token = new_token + + frame.update_token = update_token selected_file_path = [None] @@ -37,14 +42,14 @@ def create_shop_frame(parent, switch_func, API_URL, token): messagebox.showerror("File Error", f"Unable to open file: {str(e)}") return - if not token: + if not frame.access_token: messagebox.showerror( "Token Error", "Access token not found. Please log in." ) return - headers = {"Authorization": f"Bearer {token}"} - print(f"Access Token in create_shop: {token}") # Debugging line + headers = {"Authorization": f"Bearer {frame.access_token}"} + print(f"Access Token in create_shop: {frame.access_token}") # Debugging line try: response = requests.post(url, data=data, files=files, headers=headers) @@ -90,4 +95,4 @@ def create_shop_frame(parent, switch_func, API_URL, token): command=lambda: switch_func("login"), ).pack(pady=5) - return frame + return frame \ No newline at end of file diff --git a/app/frontend/components/shop/view_shop.py b/app/frontend/components/shop/view_shop.py index a5b9141d56308133b285a7556cc7e7867ddf8bf8..e7aa1583c30e38415253056ba8e7ed16b370d531 100644 --- a/app/frontend/components/shop/view_shop.py +++ b/app/frontend/components/shop/view_shop.py @@ -35,7 +35,7 @@ def view_shop_frame(parent, switch_func, API_URL, token): headers = {"Authorization": f"Bearer {token}"} try: response = requests.get( - f"{API_URL}/shops/my-shop", headers=headers + f"{API_URL}/shops/1", headers=headers ) # Adjust the endpoint as needed if response.status_code == 200: shop_data = response.json() @@ -137,4 +137,4 @@ def view_shop_frame(parent, switch_func, API_URL, token): # Fetch shop data on load fetch_shop_data() - return frame + return frame \ No newline at end of file diff --git a/app/frontend/main.py b/app/frontend/main.py index d81ac74a3c1ff9df76b986e0c85679b3cd2635ee..eb16a15f11665ded3295e759c28855c43fa6b621 100644 --- a/app/frontend/main.py +++ b/app/frontend/main.py @@ -1,3 +1,4 @@ +# main.py import customtkinter as ctk from components.auth.login import login_frame from components.auth.register import register_frame @@ -5,40 +6,37 @@ from components.shop.create_shop import create_shop_frame from components.shop.view_shop import view_shop_frame from components.product.create_product import product_frame from components.admin.category import category_frame +from components.dashboard import dashboard_frame # import the dashboard -# Backend API URL API_URL = "http://127.0.0.1:8000" - -# Global variable to store the access token access_token = None - -# Function to switch between frames def switch_frame(frame_name, token=None): global access_token if token: access_token = token - frames.get(frame_name, login).tkraise() # Default to login if frame_name is invalid - + frame = frames.get(frame_name, login) + if hasattr(frame, 'update_token'): + frame.update_token(access_token) + frame.tkraise() -# Create main window -ctk.set_appearance_mode("dark") # Light, Dark, or System +ctk.set_appearance_mode("dark") ctk.set_default_color_theme("blue") root = ctk.CTk() root.title("Shopping App") root.geometry("900x800") -# Create Frames inside the main window +# Create Frames login = login_frame(root, switch_frame, API_URL) register = register_frame(root, switch_frame, API_URL) create_shop = create_shop_frame(root, switch_frame, API_URL, access_token) view_shop = view_shop_frame(root, switch_frame, API_URL, access_token) product = 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) # new dashboard frame -# Store frames in a dictionary for easier management frames = { "login": login, "register": register, @@ -46,13 +44,13 @@ frames = { "create_product": product, "category": category, "view_shop": view_shop, + "dashboard": dashboard, # add dashboard here } -# Place all frames responsively for frame in frames.values(): - frame.place(relx=0, rely=0.2, relwidth=1, relheight=0.8) + frame.place(relx=0, rely=0, relwidth=1, relheight=0.8) -# Show the login frame first -switch_frame("login") +# Show the login frame first (or switch to dashboard as needed) +switch_frame("login") # switch to dashboard root.mainloop() diff --git a/app/static/default_shop_image.png b/app/static/default_shop_image.png deleted file mode 100644 index cbe892f8fcae34a8022b471f94c9ef8b7140d7d6..0000000000000000000000000000000000000000 Binary files a/app/static/default_shop_image.png and /dev/null differ