diff --git a/app/frontend/components/admin/category.py b/app/frontend/components/admin/category.py new file mode 100644 index 0000000000000000000000000000000000000000..29c239edd842ce2052b965c4c4ad1bd86d79c67f --- /dev/null +++ b/app/frontend/components/admin/category.py @@ -0,0 +1,117 @@ +import customtkinter as ctk +import requests +from tkinter import messagebox + + +def category_frame(parent, switch_func, API_URL, access_token): + frame = ctk.CTkFrame(parent) + + ctk.CTkLabel( + frame, text="Category Management", font=("Helvetica", 18, "bold") + ).pack(pady=10) + + ctk.CTkLabel(frame, text="Category Name:").pack(pady=5) + entry_name = ctk.CTkEntry(frame) + entry_name.pack(pady=5) + + ctk.CTkLabel(frame, text="Category ID (for update/delete):").pack(pady=5) + entry_id = ctk.CTkEntry(frame) + entry_id.pack(pady=5) + + headers = {"Authorization": f"Bearer {access_token}"} if access_token else {} + + def create_category(): + name = entry_name.get().strip() + if not name: + messagebox.showwarning("Input Error", "Category name is required!") + return + + try: + response = requests.post( + f"{API_URL}/category", data={"name": name}, headers=headers + ) + if response.status_code == 200: + messagebox.showinfo( + "Success", f"Category '{name}' created successfully!" + ) + entry_name.delete(0, "end") + else: + messagebox.showerror( + "Error", response.json().get("detail", "Failed to create category") + ) + except requests.exceptions.RequestException as e: + messagebox.showerror("Error", f"Failed to connect to server: {e}") + + def list_categories(): + try: + response = requests.get(f"{API_URL}/category", headers=headers) + if response.status_code == 200: + categories = response.json() + if categories: + category_list = "\n".join( + [f"{cat['id']}: {cat['name']}" for cat in categories] + ) + messagebox.showinfo("Categories", category_list) + else: + messagebox.showinfo("Categories", "No categories available.") + else: + messagebox.showerror("Error", "Failed to fetch categories") + except requests.exceptions.RequestException as e: + messagebox.showerror("Error", f"Failed to connect to server: {e}") + + def update_category(): + category_id = entry_id.get().strip() + name = entry_name.get().strip() + if not category_id.isdigit(): + messagebox.showwarning("Input Error", "Enter a valid Category ID!") + return + if not name: + messagebox.showwarning( + "Input Error", "Category name is required for update!" + ) + return + + try: + response = requests.put( + f"{API_URL}/category/{category_id}", + data={"name": name}, + headers=headers, + ) + if response.status_code == 200: + messagebox.showinfo("Success", "Category updated successfully!") + entry_id.delete(0, "end") + entry_name.delete(0, "end") + else: + messagebox.showerror( + "Error", response.json().get("detail", "Failed to update category") + ) + except requests.exceptions.RequestException as e: + messagebox.showerror("Error", f"Failed to connect to server: {e}") + + def delete_category(): + category_id = entry_id.get().strip() + if not category_id.isdigit(): + messagebox.showwarning("Input Error", "Enter a valid Category ID!") + return + + try: + response = requests.delete( + f"{API_URL}/category/{category_id}", headers=headers + ) + if response.status_code == 200: + messagebox.showinfo("Success", "Category deleted successfully!") + entry_id.delete(0, "end") + else: + messagebox.showerror( + "Error", response.json().get("detail", "Failed to delete category") + ) + except requests.exceptions.RequestException as e: + messagebox.showerror("Error", f"Failed to connect to server: {e}") + + ctk.CTkButton(frame, text="Create Category", command=create_category).pack(pady=5) + ctk.CTkButton(frame, text="List Categories", command=list_categories).pack(pady=5) + ctk.CTkButton(frame, text="Update Category", command=update_category).pack(pady=5) + ctk.CTkButton(frame, text="Delete Category", command=delete_category).pack(pady=5) + ctk.CTkButton(frame, text="Back", command=lambda: switch_func("")).pack(pady=5) + + return frame diff --git a/app/frontend/components/forgot_pass.py b/app/frontend/components/auth/forgot_pass.py similarity index 100% rename from app/frontend/components/forgot_pass.py rename to app/frontend/components/auth/forgot_pass.py diff --git a/app/frontend/components/login.py b/app/frontend/components/auth/login.py similarity index 58% rename from app/frontend/components/login.py rename to app/frontend/components/auth/login.py index bedad7b7de9b45cd2dce7b3f3332d30427001d23..64a9f81c32cf23e640fc4999041a6574144781a6 100644 --- a/app/frontend/components/login.py +++ b/app/frontend/components/auth/login.py @@ -1,17 +1,12 @@ -import ttkbootstrap as tb -import ttkbootstrap.constants +import customtkinter as ctk from tkinter import messagebox import requests -# Global variable to store the access token -access_token = None - -def login_frame(parent, switch_func, api_url): - frame = tb.Frame(parent) +def login_frame(parent, switch_func, API_URL): + frame = ctk.CTkFrame(parent) def login(): - global access_token email = entry_email.get() password = entry_password.get() @@ -20,15 +15,13 @@ def login_frame(parent, switch_func, api_url): return response = requests.post( - f"{api_url}/auth/login", json={"email": email, "password": password} + f"{API_URL}/auth/login", json={"email": email, "password": password} ) try: response_data = response.json() if response.status_code == 200: access_token = response_data["access_token"] - print(f"Access Token: {access_token}") # Debugging line - messagebox.showinfo("Login Successful", f"Welcome back, {email}!") switch_func("create_shop", access_token) else: @@ -38,23 +31,21 @@ def login_frame(parent, switch_func, api_url): except requests.exceptions.JSONDecodeError: messagebox.showerror("Login Failed", "Server returned an invalid response.") - tb.Label(frame, text="Login", font=("Helvetica", 18, "bold")).pack(pady=10) + ctk.CTkLabel(frame, text="Login", font=("Helvetica", 18, "bold")).pack(pady=10) - tb.Label(frame, text="Email:").pack(pady=5) - entry_email = tb.Entry(frame, bootstyle="info") + ctk.CTkLabel(frame, text="Email:").pack(pady=5) + entry_email = ctk.CTkEntry(frame) entry_email.pack(pady=5) - tb.Label(frame, text="Password:").pack(pady=5) - entry_password = tb.Entry(frame, bootstyle="info", show="*") + ctk.CTkLabel(frame, text="Password:").pack(pady=5) + entry_password = ctk.CTkEntry(frame, show="*") entry_password.pack(pady=5) - btn_login = tb.Button(frame, text="Login", bootstyle="primary", command=login) - btn_login.pack(pady=15) + ctk.CTkButton(frame, text="Login", command=login).pack(pady=15) - tb.Button( + ctk.CTkButton( frame, text="Don't have an account? Register", - bootstyle="link", command=lambda: switch_func("register"), ).pack() diff --git a/app/frontend/components/register.py b/app/frontend/components/auth/register.py similarity index 58% rename from app/frontend/components/register.py rename to app/frontend/components/auth/register.py index b8aa074c11b1bfcd5a3e7f7e5264692ad1c671aa..e09813251f354b657e5adcf5ca88ca3d747cfadb 100644 --- a/app/frontend/components/register.py +++ b/app/frontend/components/auth/register.py @@ -1,11 +1,10 @@ -import ttkbootstrap as tb -import ttkbootstrap.constants +import customtkinter as ctk from tkinter import messagebox -import requests # Import requests for API communication +import requests -def register_frame(parent, switch_func, api_url): # Added api_url parameter - frame = tb.Frame(parent) +def register_frame(parent, switch_func, API_URL): + frame = ctk.CTkFrame(parent) def register(): username = entry_username.get() @@ -28,9 +27,8 @@ def register_frame(parent, switch_func, api_url): # Added api_url parameter messagebox.showerror("Password Error", "Passwords do not match!") return - # Sending registration data to backend response = requests.post( - f"{api_url}/auth/signup", + f"{API_URL}/auth/signup", json={ "username": username, "email": email, @@ -43,7 +41,7 @@ def register_frame(parent, switch_func, api_url): # Added api_url parameter response_data = response.json() if response.status_code == 200: messagebox.showinfo("Registration Successful", f"Welcome, {username}!") - switch_func("login") # Switch to login after successful registration + switch_func("login") else: messagebox.showerror( "Registration Failed", response_data.get("detail", "Unknown error") @@ -53,37 +51,33 @@ def register_frame(parent, switch_func, api_url): # Added api_url parameter "Registration Failed", "Server returned an invalid response." ) - tb.Label(frame, text="Register", font=("Helvetica", 18, "bold")).pack(pady=10) + ctk.CTkLabel(frame, text="Register", font=("Helvetica", 18, "bold")).pack(pady=10) - tb.Label(frame, text="Username:").pack(pady=5) - entry_username = tb.Entry(frame, bootstyle="info") + ctk.CTkLabel(frame, text="Username:").pack(pady=5) + entry_username = ctk.CTkEntry(frame) entry_username.pack(pady=5) - tb.Label(frame, text="Email:").pack(pady=5) - entry_email = tb.Entry(frame, bootstyle="info") + ctk.CTkLabel(frame, text="Email:").pack(pady=5) + entry_email = ctk.CTkEntry(frame) entry_email.pack(pady=5) - tb.Label(frame, text="Phone Number:").pack(pady=5) - entry_phone = tb.Entry(frame, bootstyle="info") + ctk.CTkLabel(frame, text="Phone Number:").pack(pady=5) + entry_phone = ctk.CTkEntry(frame) entry_phone.pack(pady=5) - tb.Label(frame, text="Password:").pack(pady=5) - entry_password = tb.Entry(frame, bootstyle="info", show="*") + ctk.CTkLabel(frame, text="Password:").pack(pady=5) + entry_password = ctk.CTkEntry(frame, show="*") entry_password.pack(pady=5) - tb.Label(frame, text="Confirm Password:").pack(pady=5) - entry_confirm_password = tb.Entry(frame, bootstyle="info", show="*") + ctk.CTkLabel(frame, text="Confirm Password:").pack(pady=5) + entry_confirm_password = ctk.CTkEntry(frame, show="*") entry_confirm_password.pack(pady=5) - btn_register = tb.Button( - frame, text="Register", bootstyle="success", command=register - ) - btn_register.pack(pady=15) + ctk.CTkButton(frame, text="Register", command=register).pack(pady=15) - tb.Button( + ctk.CTkButton( frame, text="Already have an account? Login", - bootstyle="link", command=lambda: switch_func("login"), ).pack() diff --git a/app/frontend/components/product/create_product.py b/app/frontend/components/product/create_product.py new file mode 100644 index 0000000000000000000000000000000000000000..b95400c653b1672004fa9f84d811daf2acee967a --- /dev/null +++ b/app/frontend/components/product/create_product.py @@ -0,0 +1,69 @@ +import customtkinter as ctk +import requests +from tkinter import messagebox + + +def product_frame(parent, switch_func, API_URL, token): + frame = ctk.CTkFrame(parent) + + ctk.CTkLabel(frame, text="Product Management", font=("Helvetica", 18, "bold")).pack( + pady=10 + ) + + ctk.CTkLabel(frame, text="Product Name:").pack(pady=5) + entry_name = ctk.CTkEntry(frame) + entry_name.pack(pady=5) + + ctk.CTkLabel(frame, text="Price:").pack(pady=5) + entry_price = ctk.CTkEntry(frame) + entry_price.pack(pady=5) + + ctk.CTkLabel(frame, text="Stock:").pack(pady=5) + entry_stock = ctk.CTkEntry(frame) + entry_stock.pack(pady=5) + + def create_product(): + name = entry_name.get().strip() + price = entry_price.get().strip() + stock = entry_stock.get().strip() + + if not name or not price or not stock: + messagebox.showwarning("Input Error", "All fields are required!") + return + + try: + price = float(price) + stock = int(stock) + except ValueError: + messagebox.showwarning( + "Input Error", "Price must be a number and Stock must be an integer." + ) + return + + headers = {"Authorization": f"Bearer {token}"} + data = {"name": name, "price": price, "stock": stock} + + try: + response = requests.post(f"{API_URL}/products", json=data, headers=headers) + + if response.status_code == 200: + messagebox.showinfo( + "Success", f"Product '{name}' created successfully!" + ) + entry_name.delete(0, "end") + entry_price.delete(0, "end") + entry_stock.delete(0, "end") + else: + error_message = response.json().get( + "detail", "Failed to create product" + ) + messagebox.showerror("Error", error_message) + except requests.exceptions.RequestException as e: + messagebox.showerror("Error", f"Failed to connect to server: {e}") + + ctk.CTkButton(frame, text="Create Product", command=create_product).pack(pady=15) + ctk.CTkButton(frame, text="Back", command=lambda: switch_func("view_shop")).pack( + pady=5 + ) + + return frame diff --git a/app/frontend/components/shop/create_shop.py b/app/frontend/components/shop/create_shop.py index b4f818653b5aad140810b7b096821ca6570fc1fc..5319ebe790fe8ce412bb9eb9b5f25fa5f89f4a71 100644 --- a/app/frontend/components/shop/create_shop.py +++ b/app/frontend/components/shop/create_shop.py @@ -1,12 +1,11 @@ -import ttkbootstrap as tb -import ttkbootstrap.constants +import customtkinter as ctk from tkinter import messagebox, filedialog import requests import os -def create_shop_frame(parent, switch_func, api_url, token): - frame = tb.Frame(parent) +def create_shop_frame(parent, switch_func, API_URL, token): + frame = ctk.CTkFrame(parent) selected_file_path = [None] @@ -14,10 +13,10 @@ def create_shop_frame(parent, switch_func, api_url, token): file_path = filedialog.askopenfilename(title="Select Shop Image") if file_path: selected_file_path[0] = file_path - file_label.config(text=os.path.basename(file_path)) + file_label.configure(text=os.path.basename(file_path)) else: selected_file_path[0] = None - file_label.config(text="No file selected") + file_label.configure(text="No file selected") def create_shop(): name = entry_name.get() @@ -27,7 +26,7 @@ def create_shop_frame(parent, switch_func, api_url, token): messagebox.showwarning("Input Error", "Shop name is required!") return - url = f"{api_url}/shops" + url = f"{API_URL}/shops" data = {"name": name, "description": description} files = {} @@ -64,30 +63,30 @@ def create_shop_frame(parent, switch_func, api_url, token): except Exception as e: messagebox.showerror("Request Error", str(e)) - tb.Label(frame, text="Create Shop", font=("Helvetica", 18, "bold")).pack(pady=10) + ctk.CTkLabel(frame, text="Create Shop", font=("Helvetica", 18, "bold")).pack( + pady=10 + ) - tb.Label(frame, text="Shop Name:").pack(pady=5) - entry_name = tb.Entry(frame, bootstyle="info") + ctk.CTkLabel(frame, text="Shop Name:").pack(pady=5) + entry_name = ctk.CTkEntry(frame, placeholder_text="Enter shop name") entry_name.pack(pady=5) - tb.Label(frame, text="Description:").pack(pady=5) - entry_description = tb.Entry(frame, bootstyle="info") + ctk.CTkLabel(frame, text="Description:").pack(pady=5) + entry_description = ctk.CTkEntry(frame, placeholder_text="Enter shop description") entry_description.pack(pady=5) - tb.Button( - frame, text="Select Image", bootstyle="primary", command=select_file - ).pack(pady=5) - file_label = tb.Label(frame, text="No file selected") + ctk.CTkButton(frame, text="Select Image", command=select_file).pack(pady=5) + file_label = ctk.CTkLabel(frame, text="No file selected") file_label.pack(pady=5) - tb.Button(frame, text="Create Shop", bootstyle="success", command=create_shop).pack( - pady=15 - ) + ctk.CTkButton( + frame, text="Create Shop", fg_color="green", command=create_shop + ).pack(pady=15) - tb.Button( + ctk.CTkButton( frame, text="Back", - bootstyle="link", + fg_color="transparent", command=lambda: switch_func("login"), ).pack(pady=5) diff --git a/app/frontend/components/shop/view_shop.py b/app/frontend/components/shop/view_shop.py new file mode 100644 index 0000000000000000000000000000000000000000..a5b9141d56308133b285a7556cc7e7867ddf8bf8 --- /dev/null +++ b/app/frontend/components/shop/view_shop.py @@ -0,0 +1,140 @@ +import customtkinter as ctk +import requests +from tkinter import messagebox +from PIL import Image, ImageTk +import io + + +def view_shop_frame(parent, switch_func, API_URL, token): + frame = ctk.CTkFrame(parent) + + # Title + title_label = ctk.CTkLabel(frame, text="Your Shop", font=("Helvetica", 18, "bold")) + title_label.pack(pady=10) + + # Shop Details + shop_name_label = ctk.CTkLabel( + frame, text="Shop Name: ", font=("Helvetica", 14, "bold") + ) + shop_name_label.pack(pady=5) + + shop_description_label = ctk.CTkLabel( + frame, text="Description: ", font=("Helvetica", 12) + ) + shop_description_label.pack(pady=5) + + shop_image_label = ctk.CTkLabel(frame, text="") # Placeholder for shop image + shop_image_label.pack(pady=10) + + # Product List Section + product_list_frame = ctk.CTkFrame(frame) + product_list_frame.pack(fill="both", expand=True, padx=10, pady=10) + + def fetch_shop_data(): + """Fetch the shop created by the logged-in user""" + headers = {"Authorization": f"Bearer {token}"} + try: + response = requests.get( + f"{API_URL}/shops/my-shop", headers=headers + ) # Adjust the endpoint as needed + if response.status_code == 200: + shop_data = response.json() + shop_name_label.configure(text=f"Shop Name: {shop_data['name']}") + shop_description_label.configure( + text=f"Description: {shop_data.get('description', 'No description')}" + ) + + # Load and display shop image if available + if "image_url" in shop_data and shop_data["image_url"]: + try: + image_response = requests.get(shop_data["image_url"]) + image = Image.open(io.BytesIO(image_response.content)) + image = image.resize((150, 150)) + img_tk = ImageTk.PhotoImage(image) + + shop_image_label.configure(image=img_tk, text="") + shop_image_label.image = img_tk + except Exception: + pass + + fetch_products(shop_data["id"]) # Fetch products for this shop + else: + messagebox.showerror("Error", "Failed to fetch shop details.") + except Exception as e: + messagebox.showerror("Error", f"Request error: {e}") + + def fetch_products(shop_id): + """Fetch products that belong to the user's shop""" + headers = {"Authorization": f"Bearer {token}"} + try: + response = requests.get( + f"{API_URL}/products?shop_id={shop_id}", headers=headers + ) + if response.status_code == 200: + products = response.json() + display_products(products) + else: + messagebox.showerror("Error", "Failed to fetch products.") + except Exception as e: + messagebox.showerror("Error", f"Request error: {e}") + + def display_products(products): + """Display the list of products in the shop""" + for widget in product_list_frame.winfo_children(): + widget.destroy() + + if not products: + ctk.CTkLabel( + product_list_frame, text="No products found.", font=("Helvetica", 12) + ).pack(pady=10) + return + + for product in products: + product_frame = ctk.CTkFrame(product_list_frame) + product_frame.pack(fill="x", padx=5, pady=5) + + # Product Image + img_label = ctk.CTkLabel(product_frame, text="") # Placeholder + img_label.pack(side="left", padx=5) + + # Load and display product image + if "images" in product and product["images"]: + image_url = product["images"][0]["image_url"] + try: + image_response = requests.get(image_url) + image = Image.open(io.BytesIO(image_response.content)) + image = image.resize((50, 50)) + img_tk = ImageTk.PhotoImage(image) + + img_label.configure(image=img_tk, text="") + img_label.image = img_tk + except Exception: + pass + + # Product Details + details_frame = ctk.CTkFrame(product_frame) + details_frame.pack(side="left", fill="x", expand=True, padx=10) + + ctk.CTkLabel( + details_frame, text=product["name"], font=("Helvetica", 12, "bold") + ).pack(anchor="w") + ctk.CTkLabel( + details_frame, + text=f"Price: ${product['price']:.2f}", + font=("Helvetica", 12), + ).pack(anchor="w") + + # Refresh Data Button + refresh_button = ctk.CTkButton(frame, text="Refresh", command=fetch_shop_data) + refresh_button.pack(pady=10) + + # Back Button + back_button = ctk.CTkButton( + frame, text="Back", command=lambda: switch_func("login") + ) + back_button.pack(pady=10) + + # Fetch shop data on load + fetch_shop_data() + + return frame diff --git a/app/frontend/main.py b/app/frontend/main.py index 938dbbb5ccc6b8f3758518b53df9b00c435ca596..2b14d6bfb1226f74e7656a0e8a250797731df2b6 100644 --- a/app/frontend/main.py +++ b/app/frontend/main.py @@ -1,7 +1,10 @@ -import ttkbootstrap as tb -from components.login import login_frame -from components.register import register_frame +import customtkinter as ctk +from components.auth.login import login_frame +from components.auth.register import register_frame 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 # Backend API URL API_URL = "http://127.0.0.1:8000" @@ -16,18 +19,14 @@ def switch_frame(frame_name, token=None): if token: access_token = token - if frame_name == "login": - login.tkraise() - elif frame_name == "register": - register.tkraise() - elif frame_name == "create_shop": - create_shop = create_shop_frame(root, switch_frame, API_URL, access_token) - create_shop.place(relx=0, rely=0.2, relwidth=1, relheight=0.8) - create_shop.tkraise() + frames.get(frame_name, login).tkraise() # Default to login if frame_name is invalid # Create main window -root = tb.Window(themename="superhero") +ctk.set_appearance_mode("dark") # Light, Dark, or System +ctk.set_default_color_theme("blue") + +root = ctk.CTk() root.title("Shopping App") root.geometry("900x800") @@ -35,13 +34,25 @@ root.geometry("900x800") 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) - -# Place all frames responsively within the window. -# Adjust relx, rely, relwidth, and relheight as needed for your layout. -for frame in (login, register, create_shop): +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) + +# Store frames in a dictionary for easier management +frames = { + "login": login, + "register": register, + "create_shop": create_shop, + "create_product": product, + "category": category, + "view_shop": view_shop, +} + +# Place all frames responsively +for frame in frames.values(): frame.place(relx=0, rely=0.2, relwidth=1, relheight=0.8) # Show the login frame first -switch_frame("login") +switch_frame("view_shop") root.mainloop() diff --git a/requirements.txt b/requirements.txt index c8b79503eac507894fdbccb6dc8052bfbf436fdd..098a8e9d014d631acb415fded564d5774243a0a0 100644 Binary files a/requirements.txt and b/requirements.txt differ