diff --git a/app/frontend/components/auth/login.py b/app/frontend/components/auth/login.py index ebc0544b2be47a20d7ffa31af009580abf9a4a13..bdc8c7d1d3f3eeb5e232735ab61d6d871d88c1c1 100644 --- a/app/frontend/components/auth/login.py +++ b/app/frontend/components/auth/login.py @@ -1,6 +1,6 @@ import customtkinter as ctk import os -from tkinter import messagebox +from CTkMessagebox import CTkMessagebox from PIL import Image from utils.api_requests import login_api @@ -51,7 +51,9 @@ def login_frame(parent, switch_func, API_URL): password = entry_password.get() if not email or not password: - messagebox.showwarning("Input Error", "Both fields are required!") + CTkMessagebox( + title="Input Error", message="Both fields are required!", icon="warning" + ) return status_code, response_data = login_api(email, password, API_URL) @@ -61,13 +63,19 @@ def login_frame(parent, switch_func, API_URL): user_role = response_data.get("role") print(f"Login successful. Token: {access_token[:10]}..., Role: {user_role}") - messagebox.showinfo("Login Successful", f"Welcome back, {email}!") + CTkMessagebox( + title="Login Successful", + message=f"Welcome back, {email}!", + icon="check", + ) # Pass token and role to dashboard switch_func("dashboard", access_token) else: - messagebox.showerror( - "Login Failed", response_data.get("detail", "Invalid credentials") + CTkMessagebox( + title="Login Failed", + message=response_data.get("detail", "Invalid credentials"), + icon="cancel", ) # Login form container diff --git a/app/frontend/components/auth/register.py b/app/frontend/components/auth/register.py index dd6419923c1046334918861a596f1b02bcfcd979..9b7b571e4c97587b19d33323ec3e2afd8bb72cc6 100644 --- a/app/frontend/components/auth/register.py +++ b/app/frontend/components/auth/register.py @@ -1,6 +1,7 @@ import customtkinter as ctk -from tkinter import messagebox +from CTkMessagebox import CTkMessagebox from utils.api_requests import register_api +import re SHOPPING = "#00c1ff" DARK_BG = "#1f1f1f" @@ -49,24 +50,83 @@ def register_frame(parent, switch_func, API_URL): password = entry_password.get() confirm_password = entry_confirm_password.get() + # Basic validation if not all([username, email, phone_number, password, confirm_password]): - messagebox.showwarning("Input Error", "All fields are required!") + CTkMessagebox( + title="Input Error", message="All fields are required!", icon="warning" + ) + return + + # Email validation + email_pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$" + if not re.match(email_pattern, email): + CTkMessagebox( + title="Invalid Email", + message="Please enter a valid email address in the format: user@example.com", + icon="warning", + ) + return + + # Password validation + if len(password) < 8: + CTkMessagebox( + title="Invalid Password", + message="Password must be at least 8 characters long!", + icon="warning", + ) + return + + # Phone number validation + if not phone_number.isdigit(): + CTkMessagebox( + title="Invalid Phone Number", + message="Phone number must contain only digits (0-9)!", + icon="warning", + ) + return + + if len(phone_number) < 10: + CTkMessagebox( + title="Invalid Phone Number", + message="Phone number must be at least 10 digits long!", + icon="warning", + ) return if password != confirm_password: - messagebox.showerror("Password Error", "Passwords do not match!") + CTkMessagebox( + title="Password Error", + message="Passwords do not match! Please make sure both entries are identical.", + icon="cancel", + ) return status_code, response_data = register_api( username, email, phone_number, password, API_URL ) - if status_code == 200: - messagebox.showinfo("Registration Successful", f"Welcome, {username}!") + # Check if status_code is None (connection error) + if status_code is None: + CTkMessagebox( + title="Connection Error", + message="Could not connect to the server. Please try again later.", + icon="cancel", + ) + return + + if status_code == 200 or status_code == 201: + CTkMessagebox( + title="Registration Successful", + message=f"Welcome, {username}! You can now log in with your credentials.", + icon="check", + ) switch_func("login") else: - messagebox.showerror( - "Registration Failed", response_data.get("detail", "Unknown error") + error_message = "Unknown error" + if isinstance(response_data, dict) and "detail" in response_data: + error_message = response_data["detail"] + CTkMessagebox( + title="Registration Failed", message=error_message, icon="cancel" ) # Register form container @@ -119,7 +179,7 @@ def register_frame(parent, switch_func, API_URL): email_frame, height=40, corner_radius=8, - placeholder_text="Enter your email", + placeholder_text="Enter a valid email (user@example.com)", fg_color="#3b3b3b", border_color=SHOPPING, text_color="white", @@ -138,7 +198,7 @@ def register_frame(parent, switch_func, API_URL): phone_frame, height=40, corner_radius=8, - placeholder_text="Enter your phone number", + placeholder_text="Enter at least 10 digits (numbers only)", fg_color="#3b3b3b", border_color=SHOPPING, text_color="white", @@ -158,7 +218,7 @@ def register_frame(parent, switch_func, API_URL): height=40, corner_radius=8, show="*", - placeholder_text="Enter your password", + placeholder_text="Minimum 8 characters", fg_color="#3b3b3b", border_color=SHOPPING, text_color="white", diff --git a/app/frontend/components/dashboard.py b/app/frontend/components/dashboard.py index f115d196df0b6d995e1cc148d74f1a9804fea983..1fa30920c006c0e224df4c7bec44c9ddc3912a90 100644 --- a/app/frontend/components/dashboard.py +++ b/app/frontend/components/dashboard.py @@ -1,7 +1,7 @@ import customtkinter as ctk import requests from PIL import Image, ImageTk -from tkinter import messagebox +from CTkMessagebox import CTkMessagebox import io # import time # import threading @@ -159,7 +159,7 @@ def dashboard_frame(parent, switch_func, API_URL, token): """Call an endpoint to search products and shops by keyword.""" keyword = search_entry.get().strip() if not keyword: - messagebox.showinfo("Info", "Please enter a search keyword.") + CTkMessagebox(title="Info", message="Please enter a search keyword.") return try: headers = {"Authorization": f"Bearer {token}"} @@ -308,12 +308,14 @@ def dashboard_frame(parent, switch_func, API_URL, token): print( f"[DEBUG] Search failed: {resp.status_code}, Response: {resp.text}" ) - messagebox.showerror( - "Error", f"Search failed. Status code: {resp.status_code}" + CTkMessagebox( + title="Error", + message=f"Search failed. Status code: {resp.status_code}", + icon="cancel", ) except Exception as e: print(f"[DEBUG] Search exception: {e}") - messagebox.showerror("Error", f"Request error: {e}") + CTkMessagebox(title="Error", message=f"Request error: {e}", icon="cancel") search_button = ctk.CTkButton( search_frame, @@ -600,12 +602,15 @@ def dashboard_frame(parent, switch_func, API_URL, token): complete_product = resp.json() switch_func("view_product", complete_product) else: - messagebox.showerror( - "Error", - f"Failed to fetch product details. Status: {resp.status_code}", + CTkMessagebox( + title="Error", + message=f"Failed to fetch product details. Status: {resp.status_code}", + icon="cancel", ) except Exception as e: - messagebox.showerror("Error", f"Request error: {e}") + CTkMessagebox( + title="Error", message=f"Request error: {e}", icon="cancel" + ) else: # Normal flow for non-search results (already have complete data) switch_func("view_product", product_data) @@ -664,12 +669,13 @@ def dashboard_frame(parent, switch_func, API_URL, token): featured_shops = resp.json()[:5] # Get top 5 shops for carousel update_carousel() else: - messagebox.showerror( - "Error", - f"Failed to fetch featured shops. Status: {resp.status_code}", + CTkMessagebox( + title="Error", + message=f"Failed to fetch featured shops. Status: {resp.status_code}", + icon="cancel", ) except Exception as e: - messagebox.showerror("Error", f"Request error: {e}") + CTkMessagebox(title="Error", message=f"Request error: {e}", icon="cancel") def fetch_recommendations(): headers = {"Authorization": f"Bearer {token}"} @@ -679,12 +685,13 @@ def dashboard_frame(parent, switch_func, API_URL, token): products = resp.json() display_products(products[:12], products_frame) # Show top 12 products else: - messagebox.showerror( - "Error", - f"Failed to fetch recommended products. Status: {resp.status_code}", + CTkMessagebox( + title="Error", + message=f"Failed to fetch recommended products. Status: {resp.status_code}", + icon="cancel", ) except Exception as e: - messagebox.showerror("Error", f"Request error: {e}") + CTkMessagebox(title="Error", message=f"Request error: {e}", icon="cancel") # Function to view all shops def view_all_shops(): @@ -733,11 +740,13 @@ def dashboard_frame(parent, switch_func, API_URL, token): for i in range(max_cols): grid_frame.grid_columnconfigure(i, weight=1) else: - messagebox.showerror( - "Error", f"Failed to fetch shops. Status code: {resp.status_code}" + CTkMessagebox( + title="Error", + message=f"Failed to fetch shops. Status code: {resp.status_code}", + icon="cancel", ) except Exception as e: - messagebox.showerror("Error", f"Request error: {e}") + CTkMessagebox(title="Error", message=f"Request error: {e}", icon="cancel") # Function to create a shop card def create_shop_card(parent, shop_data): diff --git a/app/frontend/components/owner/owner_dashboard.py b/app/frontend/components/owner/owner_dashboard.py index 341019a831f9f242bf189252cfa2d4ec491990ec..d94a0704f0bf6cca70ad7be0891e8615d2c39e3a 100644 --- a/app/frontend/components/owner/owner_dashboard.py +++ b/app/frontend/components/owner/owner_dashboard.py @@ -1,6 +1,6 @@ import customtkinter as ctk import requests -from tkinter import messagebox +from CTkMessagebox import CTkMessagebox from PIL import Image, ImageTk import io @@ -607,7 +607,9 @@ def owner_dashboard_frame(parent, switch_func, API_URL, token): product_rows.append(no_shop_label) except Exception as e: print(f"Error fetching shop data: {e}") - messagebox.showerror("Error", f"Failed to load shop data: {e}") + CTkMessagebox( + title="Error", message=f"Failed to load shop data: {e}", icon="cancel" + ) # Initialize by loading data fetch_shop_data() # Load shop data on startup diff --git a/app/frontend/components/owner/owner_orders.py b/app/frontend/components/owner/owner_orders.py index 9314034d7ec22293f90b460e80d1ef0a09eb3174..d959b2c218df685088be55c39631c7f5a14823e6 100644 --- a/app/frontend/components/owner/owner_orders.py +++ b/app/frontend/components/owner/owner_orders.py @@ -1,6 +1,6 @@ import customtkinter as ctk import requests -from tkinter import messagebox +from CTkMessagebox import CTkMessagebox from PIL import Image, ImageTk import io from datetime import datetime @@ -319,9 +319,15 @@ def owner_orders_frame(parent, switch_func, API_URL, token): if is_pending: def confirm_shipment(): - if messagebox.askyesno( - "Confirm Shipment", - f"Confirm that order #{order_id} has been shipped?", + if ( + CTkMessagebox( + title="Confirm Shipment", + message=f"Confirm that order #{order_id} has been shipped?", + icon="question", + option_1="Yes", + option_2="No", + ).get() + == "Yes" ): update_order_status(order_id, "shipped") @@ -362,16 +368,22 @@ def owner_orders_frame(parent, switch_func, API_URL, token): ) if resp.status_code == 200: - messagebox.showinfo( - "Success", f"Order #{order_id} marked as {new_status}" + CTkMessagebox( + title="Success", + message=f"Order #{order_id} marked as {new_status}", + icon="info", ) # Refresh both tabs fetch_pending_orders() fetch_shipped_orders() else: - messagebox.showerror("Error", f"Failed to update order: {resp.text}") + CTkMessagebox( + title="Error", + message=f"Failed to update order: {resp.text}", + icon="cancel", + ) except Exception as e: - messagebox.showerror("Error", f"Request error: {e}") + CTkMessagebox(title="Error", message=f"Request error: {e}", icon="cancel") def fetch_pending_orders(): """Fetch pending orders for the shop owner's products""" @@ -425,13 +437,19 @@ def owner_orders_frame(parent, switch_func, API_URL, token): ) pending_order_cards.append(card) else: - messagebox.showerror( - "Error", f"Failed to fetch pending orders: {resp.text}" + CTkMessagebox( + title="Error", + message=f"Failed to fetch pending orders: {resp.text}", + icon="cancel", ) else: - messagebox.showerror("Error", f"Failed to fetch shop: {shop_resp.text}") + CTkMessagebox( + title="Error", + message=f"Failed to fetch shop: {shop_resp.text}", + icon="cancel", + ) except Exception as e: - messagebox.showerror("Error", f"Request error: {e}") + CTkMessagebox(title="Error", message=f"Request error: {e}", icon="cancel") def fetch_shipped_orders(): """Fetch shipped orders for the shop owner's products""" @@ -485,13 +503,19 @@ def owner_orders_frame(parent, switch_func, API_URL, token): ) shipped_order_cards.append(card) else: - messagebox.showerror( - "Error", f"Failed to fetch shipped orders: {resp.text}" + CTkMessagebox( + title="Error", + message=f"Failed to fetch shipped orders: {resp.text}", + icon="cancel", ) else: - messagebox.showerror("Error", f"Failed to fetch shop: {shop_resp.text}") + CTkMessagebox( + title="Error", + message=f"Failed to fetch shop: {shop_resp.text}", + icon="cancel", + ) except Exception as e: - messagebox.showerror("Error", f"Request error: {e}") + CTkMessagebox(title="Error", message=f"Request error: {e}", icon="cancel") # Initialize by fetching pending orders fetch_pending_orders() diff --git a/app/frontend/components/owner/owner_products.py b/app/frontend/components/owner/owner_products.py index bf49d962a3ce3b5dd5db0de764b0fe7fe2e8150b..71477d34fefc21f13de3ab709be56a134272b6bb 100644 --- a/app/frontend/components/owner/owner_products.py +++ b/app/frontend/components/owner/owner_products.py @@ -1,6 +1,6 @@ import customtkinter as ctk import requests -from tkinter import messagebox +from CTkMessagebox import CTkMessagebox from PIL import Image, ImageTk import io @@ -332,8 +332,15 @@ def owner_products_frame(parent, switch_func, API_URL, token): edit_btn.pack(side="left", padx=2) def delete_product(): - if messagebox.askyesno( - "Confirm Delete", f"Delete product {product['name']}?" + if ( + CTkMessagebox( + title="Confirm Delete", + message=f"Delete product {product['name']}?", + icon="question", + option_1="Yes", + option_2="No", + ).get() + == "Yes" ): headers = {"Authorization": f"Bearer {token}"} try: @@ -341,7 +348,11 @@ def owner_products_frame(parent, switch_func, API_URL, token): f"{API_URL}/product/delete/{product['id']}", headers=headers ) if resp.status_code == 200: - messagebox.showinfo("Success", "Product deleted successfully") + CTkMessagebox( + title="Success", + message="Product deleted successfully", + icon="info", + ) # Refresh both the product list and the dashboard fetch_products() # Refresh the product list @@ -359,23 +370,39 @@ def owner_products_frame(parent, switch_func, API_URL, token): ) # Ask if user wants to archive the product instead - if messagebox.askyesno( - "Cannot Delete Product", - f"{error_message}\n\nWould you like to archive this product instead (set stock to 0)?", + if ( + CTkMessagebox( + title="Cannot Delete Product", + message=f"{error_message}\n\nWould you like to archive this product instead (set stock to 0)?", + icon="warning", + option_1="Yes", + option_2="No", + ).get() + == "Yes" ): archive_product(product["id"]) except: - if messagebox.askyesno( - "Cannot Delete Product", - "This product cannot be deleted because it has been ordered by customers.\n\nWould you like to archive this product instead (set stock to 0)?", + if ( + CTkMessagebox( + title="Cannot Delete Product", + message="This product cannot be deleted because it has been ordered by customers.\n\nWould you like to archive this product instead (set stock to 0)?", + icon="warning", + option_1="Yes", + option_2="No", + ).get() + == "Yes" ): archive_product(product["id"]) else: - messagebox.showerror( - "Error", f"Failed to delete product: {resp.text}" + CTkMessagebox( + title="Error", + message=f"Failed to delete product: {resp.text}", + icon="cancel", ) except Exception as e: - messagebox.showerror("Error", f"Request error: {e}") + CTkMessagebox( + title="Error", message=f"Request error: {e}", icon="cancel" + ) def archive_product(product_id): """Set a product's stock to 0 to effectively archive it""" @@ -415,8 +442,10 @@ def owner_products_frame(parent, switch_func, API_URL, token): ) if update_resp.status_code == 200: - messagebox.showinfo( - "Success", "Product archived successfully (stock set to 0)" + CTkMessagebox( + title="Success", + message="Product archived successfully (stock set to 0)", + icon="info", ) # Refresh both the product list and the dashboard @@ -427,15 +456,21 @@ def owner_products_frame(parent, switch_func, API_URL, token): root.after(300, lambda: switch_func("owner_dashboard", True)) root.after(400, lambda: switch_func("owner_products")) else: - messagebox.showerror( - "Error", f"Failed to archive product: {update_resp.text}" + CTkMessagebox( + title="Error", + message=f"Failed to archive product: {update_resp.text}", + icon="cancel", ) else: - messagebox.showerror( - "Error", f"Failed to get product data: {get_resp.text}" + CTkMessagebox( + title="Error", + message=f"Failed to get product data: {get_resp.text}", + icon="cancel", ) except Exception as e: - messagebox.showerror("Error", f"Request error: {e}") + CTkMessagebox( + title="Error", message=f"Request error: {e}", icon="cancel" + ) finally: if "files" in locals() and files and "images" in files: files["images"].close() @@ -510,8 +545,10 @@ def owner_products_frame(parent, switch_func, API_URL, token): product_rows.append(no_products_label) else: print(f"Error fetching products: {prod_resp.status_code}") - messagebox.showerror( - "Error", f"Failed to fetch products: {prod_resp.text}" + CTkMessagebox( + title="Error", + message=f"Failed to fetch products: {prod_resp.text}", + icon="cancel", ) else: print("No shops found for this owner") @@ -525,10 +562,14 @@ def owner_products_frame(parent, switch_func, API_URL, token): product_rows.append(no_shop_label) else: print(f"Error fetching shop: {shop_resp.status_code}") - messagebox.showerror("Error", f"Failed to fetch shop: {shop_resp.text}") + CTkMessagebox( + title="Error", + message=f"Failed to fetch shop: {shop_resp.text}", + icon="cancel", + ) except Exception as e: print(f"Error in fetch_products: {e}") - messagebox.showerror("Error", f"Request error: {e}") + CTkMessagebox(title="Error", message=f"Request error: {e}", icon="cancel") # Load products on initialization fetch_products() diff --git a/app/frontend/components/product/create_product.py b/app/frontend/components/product/create_product.py index 68e8f1b3eec7945a0849f6f32907ec2332b98d56..57bd41fc2ee2da586421eab49ba2d5806e4430ac 100644 --- a/app/frontend/components/product/create_product.py +++ b/app/frontend/components/product/create_product.py @@ -1,5 +1,6 @@ import customtkinter as ctk -from tkinter import filedialog, messagebox +from tkinter import filedialog +from CTkMessagebox import CTkMessagebox import requests import os from PIL import Image, ImageTk @@ -174,12 +175,15 @@ def create_product_frame(parent, switch_func, API_URL, token): if category_names: category_dropdown.set(category_names[0]) else: - messagebox.showerror( - "Error", - f"Failed to fetch categories. Status: {response.status_code}", + CTkMessagebox( + title="Error", + message=f"Failed to fetch categories. Status: {response.status_code}", + icon="cancel", ) except Exception as e: - messagebox.showerror("Error", f"Failed to fetch categories: {e}") + CTkMessagebox( + title="Error", message=f"Failed to fetch categories: {e}", icon="cancel" + ) category_dropdown.configure(values=["Error loading categories"]) category_dropdown.set("Error loading categories") @@ -274,19 +278,31 @@ def create_product_frame(parent, switch_func, API_URL, token): category_id = categories_data.get(category_name) if not title_val or not price_val or not stock_val: - messagebox.showerror("Error", "Title, Price, and Stock are required") + CTkMessagebox( + title="Error", + message="Title, Price, and Stock are required", + icon="cancel", + ) return # Validate that price and stock are positive numbers try: price = float(price_val) if price <= 0: - messagebox.showerror("Invalid Price", "Price must be greater than zero") + CTkMessagebox( + title="Invalid Price", + message="Price must be greater than zero", + icon="cancel", + ) price_entry.delete(0, "end") price_entry.focus() return except ValueError: - messagebox.showerror("Invalid Price", "Price must be a valid number") + CTkMessagebox( + title="Invalid Price", + message="Price must be a valid number", + icon="cancel", + ) price_entry.delete(0, "end") price_entry.focus() return @@ -294,15 +310,19 @@ def create_product_frame(parent, switch_func, API_URL, token): try: stock = int(stock_val) if stock <= 0: - messagebox.showerror( - "Invalid Stock", "Stock quantity must be greater than zero" + CTkMessagebox( + title="Invalid Stock", + message="Stock quantity must be greater than zero", + icon="cancel", ) stock_entry.delete(0, "end") stock_entry.focus() return except ValueError: - messagebox.showerror( - "Invalid Stock", "Stock quantity must be a valid integer" + CTkMessagebox( + title="Invalid Stock", + message="Stock quantity must be a valid integer", + icon="cancel", ) stock_entry.delete(0, "end") stock_entry.focus() @@ -314,15 +334,19 @@ def create_product_frame(parent, switch_func, API_URL, token): # Fetch the owner's shop shop_resp = requests.get(f"{API_URL}/shops/owner", headers=headers) if shop_resp.status_code != 200: - messagebox.showerror( - "Error", f"Failed to get shop data: {shop_resp.text}" + CTkMessagebox( + title="Error", + message=f"Failed to get shop data: {shop_resp.text}", + icon="cancel", ) return shops = shop_resp.json() if not shops: - messagebox.showerror( - "Error", "You don't have any shops. Please create a shop first." + CTkMessagebox( + title="Error", + message="You don't have any shops. Please create a shop first.", + icon="cancel", ) return @@ -345,7 +369,11 @@ def create_product_frame(parent, switch_func, API_URL, token): f"{API_URL}/product/create", data=data, files=files, headers=headers ) if resp.status_code == 200: - messagebox.showinfo("Success", "Product created successfully!") + CTkMessagebox( + title="Success", + message="Product created successfully!", + icon="check", + ) # Switch to owner_products and tell it to refresh switch_func("owner_products", True) @@ -355,12 +383,13 @@ def create_product_frame(parent, switch_func, API_URL, token): root.after(100, lambda: switch_func("owner_dashboard", True)) root.after(200, lambda: switch_func("owner_products", True)) else: - messagebox.showerror( - "Error", - f"Failed to create product. Status: {resp.status_code}\n{resp.text}", + CTkMessagebox( + title="Error", + message=f"Failed to create product. Status: {resp.status_code}\n{resp.text}", + icon="cancel", ) except Exception as e: - messagebox.showerror("Error", f"Request error: {e}") + CTkMessagebox(title="Error", message=f"Request error: {e}", icon="cancel") finally: if files and "images" in files: files["images"].close() diff --git a/app/frontend/components/product/edit_product.py b/app/frontend/components/product/edit_product.py index c15a367bfc8e6c2add878643b77cf7db23976bfe..a2aa28e1e37ac3ea77993f88c8bf72361225a0fb 100644 --- a/app/frontend/components/product/edit_product.py +++ b/app/frontend/components/product/edit_product.py @@ -1,5 +1,6 @@ import customtkinter as ctk -from tkinter import filedialog, messagebox +from tkinter import filedialog +from CTkMessagebox import CTkMessagebox import requests import os from PIL import Image, ImageTk @@ -187,12 +188,15 @@ def edit_product_frame(parent, switch_func, API_URL, token, product_id=None): elif category_names: category_dropdown.set(category_names[0]) else: - messagebox.showerror( - "Error", - f"Failed to fetch categories. Status: {response.status_code}", + CTkMessagebox( + title="Error", + message=f"Failed to fetch categories. Status: {response.status_code}", + icon="cancel", ) except Exception as e: - messagebox.showerror("Error", f"Failed to fetch categories: {e}") + CTkMessagebox( + title="Error", message=f"Failed to fetch categories: {e}", icon="cancel" + ) category_dropdown.configure(values=["Error loading categories"]) category_dropdown.set("Error loading categories") @@ -300,19 +304,31 @@ def edit_product_frame(parent, switch_func, API_URL, token, product_id=None): selected_category_id = categories_data.get(category_name, None) if not title_val or not price_val or not stock_val: - messagebox.showerror("Error", "Title, Price, and Stock are required") + CTkMessagebox( + title="Error", + message="Title, Price, and Stock are required", + icon="cancel", + ) return # Validate that price and stock are positive numbers try: price = float(price_val) if price <= 0: - messagebox.showerror("Invalid Price", "Price must be greater than zero") + CTkMessagebox( + title="Invalid Price", + message="Price must be greater than zero", + icon="cancel", + ) price_entry.delete(0, "end") price_entry.focus() return except ValueError: - messagebox.showerror("Invalid Price", "Price must be a valid number") + CTkMessagebox( + title="Invalid Price", + message="Price must be a valid number", + icon="cancel", + ) price_entry.delete(0, "end") price_entry.focus() return @@ -320,15 +336,19 @@ def edit_product_frame(parent, switch_func, API_URL, token, product_id=None): try: stock = int(stock_val) if stock <= 0: - messagebox.showerror( - "Invalid Stock", "Stock quantity must be greater than zero" + CTkMessagebox( + title="Invalid Stock", + message="Stock quantity must be greater than zero", + icon="cancel", ) stock_entry.delete(0, "end") stock_entry.focus() return except ValueError: - messagebox.showerror( - "Invalid Stock", "Stock quantity must be a valid integer" + CTkMessagebox( + title="Invalid Stock", + message="Stock quantity must be a valid integer", + icon="cancel", ) stock_entry.delete(0, "end") stock_entry.focus() @@ -376,8 +396,10 @@ def edit_product_frame(parent, switch_func, API_URL, token, product_id=None): # Check if product_id is None if product_id is None: - messagebox.showerror( - "Error", "Product ID is missing. Cannot update product." + CTkMessagebox( + title="Error", + message="Product ID is missing. Cannot update product.", + icon="cancel", ) return @@ -389,7 +411,11 @@ def edit_product_frame(parent, switch_func, API_URL, token, product_id=None): headers=headers, ) if resp.status_code == 200: - messagebox.showinfo("Success", "Product updated successfully!") + CTkMessagebox( + title="Success", + message="Product updated successfully!", + icon="check", + ) # Refresh both the owner_products and owner_dashboard frames # We do this by switching back to owner_products with refresh flag, @@ -406,12 +432,13 @@ def edit_product_frame(parent, switch_func, API_URL, token, product_id=None): # Finally, switch back to owner_products so user sees the product list root.after(400, lambda: switch_func("owner_products")) else: - messagebox.showerror( - "Error", - f"Failed to update product. Status: {resp.status_code}\n{resp.text}", + CTkMessagebox( + title="Error", + message=f"Failed to update product. Status: {resp.status_code}\n{resp.text}", + icon="cancel", ) except Exception as e: - messagebox.showerror("Error", f"Request error: {e}") + CTkMessagebox(title="Error", message=f"Request error: {e}", icon="cancel") finally: if files and "images" in files: files["images"].close() @@ -447,7 +474,9 @@ def edit_product_frame(parent, switch_func, API_URL, token, product_id=None): nonlocal current_image_url, shop_id, category_id, image_path if not product_id: - messagebox.showerror("Error", "No product ID provided") + CTkMessagebox( + title="Error", message="No product ID provided", icon="cancel" + ) go_back() return @@ -509,13 +538,14 @@ def edit_product_frame(parent, switch_func, API_URL, token, product_id=None): preview_label.configure(image=None, text="No image available") browse_button.configure(text="Add Image") else: - messagebox.showerror( - "Error", - f"Failed to load product data. Status: {resp.status_code}\n{resp.text}", + CTkMessagebox( + title="Error", + message=f"Failed to load product data. Status: {resp.status_code}\n{resp.text}", + icon="cancel", ) go_back() except Exception as e: - messagebox.showerror("Error", f"Request error: {e}") + CTkMessagebox(title="Error", message=f"Request error: {e}", icon="cancel") go_back() # Initialize variables needed for editing @@ -542,7 +572,11 @@ def edit_product_frame(parent, switch_func, API_URL, token, product_id=None): load_product_data() else: print("WARNING - No product ID available, cannot load product data") - messagebox.showerror("Error", "No product ID available to load data") + CTkMessagebox( + title="Error", + message="No product ID available to load data", + icon="cancel", + ) go_back() frame.refresh_data = refresh_data diff --git a/app/frontend/components/product/view_product.py b/app/frontend/components/product/view_product.py index d7bac1aba0107975bd186228885cbfb2d81af604..47cfd52af13adb50b9b7c29d7128c02df25dfd07 100644 --- a/app/frontend/components/product/view_product.py +++ b/app/frontend/components/product/view_product.py @@ -1,7 +1,7 @@ import customtkinter as ctk import requests from PIL import Image, ImageTk -from tkinter import messagebox +from CTkMessagebox import CTkMessagebox import io from typing import List, Dict, Any @@ -91,12 +91,14 @@ def product_view_shop_frame(parent, switch_func, API_URL, token, shop_id): print(f"[DEBUG] Exception fetching product {product_id}: {str(e)}") products.append(product) # Fall back to basic product data else: - messagebox.showerror( - "Error", f"Failed to fetch shop data. Status: {response.status_code}" + CTkMessagebox( + title="Error", + message=f"Failed to fetch shop data. Status: {response.status_code}", + icon="cancel", ) return frame except Exception as e: - messagebox.showerror("Error", f"Request error: {e}") + CTkMessagebox(title="Error", message=f"Request error: {e}", icon="cancel") return frame # ------------- TOP LAYER (Shop Image and Details) ------------- @@ -302,15 +304,16 @@ def product_view_shop_frame(parent, switch_func, API_URL, token, shop_id): print( f"[DEBUG] Failed to fetch product {product_id}: {resp.status_code}" ) - messagebox.showerror( - "Error", - f"Failed to fetch product details. Status: {resp.status_code}", + CTkMessagebox( + title="Error", + message=f"Failed to fetch product details. Status: {resp.status_code}", + icon="cancel", ) # Still try to show the product with available data as fallback switch_func("view_product", product_data) except Exception as e: print(f"[DEBUG] Exception in view_product_details: {str(e)}") - messagebox.showerror("Error", f"Request error: {e}") + CTkMessagebox(title="Error", message=f"Request error: {e}", icon="cancel") # Fallback to original data if exception occurs switch_func("view_product", product_data) @@ -678,17 +681,23 @@ def view_product_frame( # "Add to Cart" button def add_to_cart(): if not token: - if messagebox.askyesno( - "Login Required", - "You need to be logged in to add items to your cart. Do you want to log in now?", - ): + result = CTkMessagebox( + title="Login Required", + message="You need to be logged in to add items to your cart. Do you want to log in now?", + icon="question", + option_1="Yes", + option_2="No", + ).get() + if result == "Yes": switch_func("login") return # Validate quantity again just to be safe if quantity < 1 or quantity > product_stock: - messagebox.showerror( - "Invalid Quantity", "Please select a valid quantity" + CTkMessagebox( + title="Invalid Quantity", + message="Please select a valid quantity", + icon="cancel", ) return @@ -705,18 +714,23 @@ def view_product_frame( f"{API_URL}/cart/add", json=data, headers=headers ) if resp.status_code == 200: - messagebox.showinfo( - "Success", "Item added to cart successfully!" + CTkMessagebox( + title="Success", + message="Item added to cart successfully!", + icon="check", ) # Navigate to the My Orders page switch_func("user_orders") else: - messagebox.showerror( - "Error", - f"Failed to add item to cart. Status: {resp.status_code}\n{resp.text}", + CTkMessagebox( + title="Error", + message=f"Failed to add item to cart. Status: {resp.status_code}\n{resp.text}", + icon="cancel", ) except Exception as e: - messagebox.showerror("Error", f"Request error: {e}") + CTkMessagebox( + title="Error", message=f"Request error: {e}", icon="cancel" + ) add_to_cart_btn = ctk.CTkButton( details_inner, diff --git a/app/frontend/components/shop/create_shop.py b/app/frontend/components/shop/create_shop.py index 032d176ea5e20f29453f7e9899b04a12f8c58a0c..dd40cf9f1b9aff43cbde3d5201febdff9da625a9 100644 --- a/app/frontend/components/shop/create_shop.py +++ b/app/frontend/components/shop/create_shop.py @@ -1,7 +1,8 @@ import customtkinter as ctk import requests from PIL import Image, ImageTk -from tkinter import messagebox, filedialog +from tkinter import filedialog +from CTkMessagebox import CTkMessagebox import io import os @@ -187,7 +188,9 @@ def create_shop_frame(parent, switch_func, API_URL, token): print(f"Selected image: {file_path}") except Exception as e: print(f"Image load error: {e}") - messagebox.showerror("Error", "Cannot load the image.") + CTkMessagebox( + title="Error", message="Cannot load the image.", icon="cancel" + ) change_photo_button = ctk.CTkButton( pic_frame, @@ -209,7 +212,9 @@ def create_shop_frame(parent, switch_func, API_URL, token): shop_address = shop_address_entry.get().strip() if not all([shop_name, shop_description, shop_address]): - messagebox.showwarning("Input Error", "All fields are required!") + CTkMessagebox( + title="Input Error", message="All fields are required!", icon="warning" + ) return headers = {"Authorization": f"Bearer {token}"} @@ -249,27 +254,35 @@ def create_shop_frame(parent, switch_func, API_URL, token): if resp.status_code == 200: # Show success message for shop creation - messagebox.showinfo( - "Success", - "Shop created successfully! You are now a shop owner.", + CTkMessagebox( + title="Success", + message="Shop created successfully! You are now a shop owner.", + icon="check", ) # Notify user to log in again - messagebox.showinfo( - "Login Required", "Please log in again to proceed." + CTkMessagebox( + title="Login Required", + message="Please log in again to proceed.", + icon="info", ) # Redirect to login screen switch_func("login") else: print(f"Error creating shop: {resp.status_code}, {resp.text}") - messagebox.showerror( - "Error", - f"Failed to create shop. Status: {resp.status_code}", + CTkMessagebox( + title="Error", + message=f"Failed to create shop. Status: {resp.status_code}", + icon="cancel", ) except Exception as e: print(f"Error sending shop data with image: {e}") - messagebox.showerror("Error", f"Error uploading shop data: {str(e)}") + CTkMessagebox( + title="Error", + message=f"Error uploading shop data: {str(e)}", + icon="cancel", + ) else: # No image selected, proceed with text fields only try: @@ -280,26 +293,33 @@ def create_shop_frame(parent, switch_func, API_URL, token): ) if resp.status_code == 200: # Show success message for shop creation - messagebox.showinfo( - "Success", - "Shop created successfully! You are now a shop owner.", + CTkMessagebox( + title="Success", + message="Shop created successfully! You are now a shop owner.", + icon="check", ) # Notify user to log in again - messagebox.showinfo( - "Login Required", "Please log in again to proceed." + CTkMessagebox( + title="Login Required", + message="Please log in again to proceed.", + icon="info", ) # Redirect to login screen switch_func("login") else: print(f"Error creating shop: {resp.status_code}, {resp.text}") - messagebox.showerror( - "Error", f"Failed to create shop. Status: {resp.status_code}" + CTkMessagebox( + title="Error", + message=f"Failed to create shop. Status: {resp.status_code}", + icon="cancel", ) except Exception as e: print(f"Request error: {e}") - messagebox.showerror("Error", f"Request error: {e}") + CTkMessagebox( + title="Error", message=f"Request error: {e}", icon="cancel" + ) create_button = ctk.CTkButton( form_frame, diff --git a/app/frontend/components/shop/view_shop.py b/app/frontend/components/shop/view_shop.py index 5a9237da336572e971d4f496d5f95aad7820c172..e587ad3c449d1ae273fd0939a6cdb104f0d8c023 100644 --- a/app/frontend/components/shop/view_shop.py +++ b/app/frontend/components/shop/view_shop.py @@ -1,7 +1,7 @@ import customtkinter as ctk import requests from PIL import Image, ImageTk -from tkinter import messagebox +from CTkMessagebox import CTkMessagebox import io # UI Constants @@ -144,14 +144,15 @@ def view_shop_frame(parent, switch_func, API_URL, token, shop_id): ) else: print(f"[DEBUG] Error response: {response.text}") - messagebox.showerror( - "Error", - f"Failed to fetch shop data. Status: {response.status_code}", + CTkMessagebox( + title="Error", + message=f"Failed to fetch shop data. Status: {response.status_code}", + icon="cancel", ) return except Exception as e: print(f"[DEBUG] Error fetching shop data: {e}") - messagebox.showerror("Error", f"Request error: {e}") + CTkMessagebox(title="Error", message=f"Request error: {e}", icon="cancel") return # Shop Header Section diff --git a/app/frontend/components/user_details.py b/app/frontend/components/user_details.py index a689a93bb1fb46b02698ed6cff233b7afd0ff48f..dc636c6070447f0ce0fb123a81b423fbd98f222e 100644 --- a/app/frontend/components/user_details.py +++ b/app/frontend/components/user_details.py @@ -1,7 +1,8 @@ import customtkinter as ctk import requests from PIL import Image, ImageTk -from tkinter import messagebox, filedialog +from tkinter import filedialog +from CTkMessagebox import CTkMessagebox import io SHOPPING = "#00c1ff" @@ -241,7 +242,7 @@ def user_details_frame(parent, switch_func, API_URL, token): username_entry = create_form_field("Username", "Enter your username...") email_entry = create_form_field("Email", "Enter your email...") - phone_entry = create_form_field("Phone", "Enter your phone number...") + phone_entry = create_form_field("Phone", "Enter at least 10 digits (numbers only)") # Button container for save/cancel in edit mode button_container_edit = ctk.CTkFrame(form_frame, fg_color="transparent") @@ -283,27 +284,56 @@ def user_details_frame(parent, switch_func, API_URL, token): # Save and Cancel buttons in edit mode def save_profile(): + # Get and validate inputs + username = username_entry.get().strip() + phone = phone_entry.get().strip() + + # Validate phone number + if not phone.isdigit(): + CTkMessagebox( + title="Invalid Phone Number", + message="Phone number must contain only digits (0-9)!", + icon="warning", + ) + return + + if len(phone) < 10: + CTkMessagebox( + title="Invalid Phone Number", + message="Phone number must be at least 10 digits long!", + icon="warning", + ) + return + + # Prepare payload and headers headers = {"Authorization": f"Bearer {frame.token}"} payload = { - "username": username_entry.get().strip(), - "phone": phone_entry.get().strip(), # Note: backend expects "phone", not "phone_number" - # Email is intentionally omitted from the payload to prevent changes + "username": username, + "phone": phone, # Backend expects "phone" in UserUpdate schema } + try: - resp = requests.put(f"{API_URL}/user/update", headers=headers, json=payload) + resp = requests.put(f"{API_URL}/auth/update", headers=headers, json=payload) if resp.status_code == 200: - messagebox.showinfo("Success", "Profile updated successfully!") + CTkMessagebox( + title="Success", + message="Profile updated successfully!", + icon="check", + ) # Update the view labels with new data - username_view.configure(text=payload["username"]) - phone_view.configure(text=payload["phone"]) + username_view.configure(text=username) + phone_view.configure(text=phone) # Switch back to view mode cancel_edit() else: - messagebox.showerror("Error", "Unable to update profile.") + error_message = "Unable to update profile." + if resp.json() and "detail" in resp.json(): + error_message = resp.json()["detail"] + CTkMessagebox(title="Error", message=error_message, icon="cancel") except Exception as e: - messagebox.showerror("Error", f"Request error: {e}") + CTkMessagebox(title="Error", message=f"Request error: {e}", icon="cancel") save_button = ctk.CTkButton( button_container_edit, @@ -335,7 +365,7 @@ def user_details_frame(parent, switch_func, API_URL, token): f"Fetching user info with token: {frame.token[:10] if frame.token else 'None'}" ) try: - resp = requests.get(f"{API_URL}/user/profile", headers=headers) + resp = requests.get(f"{API_URL}/auth/profile", headers=headers) print(f"User profile response: {resp.status_code}") if resp.status_code == 200: data = resp.json() @@ -344,7 +374,8 @@ def user_details_frame(parent, switch_func, API_URL, token): # Update view mode displays username_view.configure(text=data.get("username", "")) email_view.configure(text=data.get("email", "")) - phone_view.configure(text=data.get("phone", "")) + # The backend returns "phone_number" from the User model + phone_view.configure(text=data.get("phone_number", "")) # Check if user is an owner and update UI accordingly nonlocal is_owner, owner_button @@ -386,10 +417,14 @@ def user_details_frame(parent, switch_func, API_URL, token): else: print(f"Error response: {resp.text}") - messagebox.showerror("Error", "Unable to retrieve user information.") + CTkMessagebox( + title="Error", + message="Unable to retrieve user information.", + icon="cancel", + ) except Exception as e: print(f"Exception in fetch_user_info: {e}") - messagebox.showerror("Error", f"Request error: {e}") + CTkMessagebox(title="Error", message=f"Request error: {e}", icon="cancel") # Initialize by fetching user info fetch_user_info() diff --git a/app/frontend/components/user_orders.py b/app/frontend/components/user_orders.py index 5b5cd18643c514037bf8ec0903df5ca4f60839b2..dcb5ba53051725aa8a90d4aaf10b5b425e7f6176 100644 --- a/app/frontend/components/user_orders.py +++ b/app/frontend/components/user_orders.py @@ -1,7 +1,7 @@ import customtkinter as ctk import requests from PIL import Image, ImageTk -from tkinter import messagebox +from CTkMessagebox import CTkMessagebox import io SHOPPING = "#00c1ff" @@ -478,31 +478,47 @@ def user_orders_frame(parent, switch_func, API_URL, token): ] if not selected_item_ids: - messagebox.showerror("Error", "Please select at least one item") + CTkMessagebox( + title="Error", + message="Please select at least one item", + icon="cancel", + ) return if ( not payment_options or payment_options[0] == "No payment methods available" ): - result = messagebox.askyesno( - "No Payment Method", - "You don't have any payment methods. Would you like to add one now?", - ) - if result: + result = CTkMessagebox( + title="No Payment Method", + message="You don't have any payment methods. Would you like to add one now?", + icon="question", + option_1="Yes", + option_2="No", + ).get() + + if result == "Yes": switch_func("user_payments") return delivery_addr = address_entry.get().strip() if not delivery_addr: - messagebox.showerror("Error", "Please enter a delivery address") + CTkMessagebox( + title="Error", + message="Please enter a delivery address", + icon="cancel", + ) return selected_payment = payment_var.get() payment_id = payment_ids.get(selected_payment) if not payment_id: - messagebox.showerror("Error", "Invalid payment method selected") + CTkMessagebox( + title="Error", + message="Invalid payment method selected", + icon="cancel", + ) return try: @@ -517,8 +533,10 @@ def user_orders_frame(parent, switch_func, API_URL, token): ) if checkout_response.status_code == 200: - messagebox.showinfo( - "Success", "Your order has been placed successfully!" + CTkMessagebox( + title="Success", + message="Your order has been placed successfully!", + icon="check", ) # Refresh order history and clear cart view load_order_history() @@ -529,12 +547,18 @@ def user_orders_frame(parent, switch_func, API_URL, token): error_msg = checkout_response.json().get( "detail", "Unknown error" ) - messagebox.showerror( - "Checkout Failed", f"Error: {error_msg}" + CTkMessagebox( + title="Checkout Failed", + message=f"Error: {error_msg}", + icon="cancel", ) except Exception as e: - messagebox.showerror("Error", f"Checkout failed: {str(e)}") + CTkMessagebox( + title="Error", + message=f"Checkout failed: {str(e)}", + icon="cancel", + ) checkout_button = ctk.CTkButton( bottom_frame, @@ -551,10 +575,16 @@ def user_orders_frame(parent, switch_func, API_URL, token): else: error_msg = response.json().get("detail", "Unknown error") - messagebox.showerror("Error", f"Failed to load cart: {error_msg}") + CTkMessagebox( + title="Error", + message=f"Failed to load cart: {error_msg}", + icon="cancel", + ) except Exception as e: - messagebox.showerror("Error", f"Failed to load cart: {str(e)}") + CTkMessagebox( + title="Error", message=f"Failed to load cart: {str(e)}", icon="cancel" + ) def create_cart_item_frame(item, checkbox_var): item_frame = ctk.CTkFrame(cart_items_frame, fg_color="#2b2b2b", corner_radius=5) @@ -674,13 +704,17 @@ def user_orders_frame(parent, switch_func, API_URL, token): load_cart_items() else: error_msg = response.json().get("detail", "Unknown error") - messagebox.showerror( - "Error", f"Failed to update quantity: {error_msg}" + CTkMessagebox( + title="Error", + message=f"Failed to update quantity: {error_msg}", + icon="cancel", ) except Exception as e: - messagebox.showerror( - "Error", f"Failed to update quantity: {str(e)}" + CTkMessagebox( + title="Error", + message=f"Failed to update quantity: {str(e)}", + icon="cancel", ) minus_btn = ctk.CTkButton( @@ -733,10 +767,18 @@ def user_orders_frame(parent, switch_func, API_URL, token): load_cart_items() # Refresh cart else: error_msg = response.json().get("detail", "Unknown error") - messagebox.showerror("Error", f"Failed to remove item: {error_msg}") + CTkMessagebox( + title="Error", + message=f"Failed to remove item: {error_msg}", + icon="cancel", + ) except Exception as e: - messagebox.showerror("Error", f"Failed to remove item: {str(e)}") + CTkMessagebox( + title="Error", + message=f"Failed to remove item: {str(e)}", + icon="cancel", + ) remove_btn = ctk.CTkButton( content, @@ -797,11 +839,19 @@ def user_orders_frame(parent, switch_func, API_URL, token): else: error_msg = response.json().get("detail", "Unknown error") print(f"Error loading orders: {error_msg}") - messagebox.showerror("Error", f"Failed to load orders: {error_msg}") + CTkMessagebox( + title="Error", + message=f"Failed to load orders: {error_msg}", + icon="cancel", + ) except Exception as e: print(f"Exception in load_order_history: {e}") - messagebox.showerror("Error", f"Failed to load order history: {str(e)}") + CTkMessagebox( + title="Error", + message=f"Failed to load order history: {str(e)}", + icon="cancel", + ) def create_order_history_frame(order): order_frame = ctk.CTkFrame(orders_frame, fg_color=CARD_BG, corner_radius=10) @@ -1001,13 +1051,19 @@ def user_orders_frame(parent, switch_func, API_URL, token): else: error_msg = response.json().get("detail", "Unknown error") print(f"Error loading shipped orders: {error_msg}") - messagebox.showerror( - "Error", f"Failed to load shipped orders: {error_msg}" + CTkMessagebox( + title="Error", + message=f"Failed to load shipped orders: {error_msg}", + icon="cancel", ) except Exception as e: print(f"Exception in load_shipped_orders: {e}") - messagebox.showerror("Error", f"Failed to load shipped orders: {str(e)}") + CTkMessagebox( + title="Error", + message=f"Failed to load shipped orders: {str(e)}", + icon="cancel", + ) def create_shipped_order_frame(order): order_frame = ctk.CTkFrame( @@ -1182,9 +1238,15 @@ def user_orders_frame(parent, switch_func, API_URL, token): button_frame.pack(fill="x", pady=(10, 5)) def mark_as_received(): - if messagebox.askyesno( - "Confirm Receipt", "Confirm that you have received this order?" - ): + result = CTkMessagebox( + title="Confirm Receipt", + message="Confirm that you have received this order?", + icon="question", + option_1="Yes", + option_2="No", + ).get() + + if result == "Yes": try: headers = {"Authorization": f"Bearer {frame.token}"} response = requests.put( @@ -1194,16 +1256,26 @@ def user_orders_frame(parent, switch_func, API_URL, token): ) if response.status_code == 200: - messagebox.showinfo("Success", "Order marked as delivered!") + CTkMessagebox( + title="Success", + message="Order marked as delivered!", + icon="check", + ) # Refresh shipped orders load_shipped_orders() else: error_msg = response.json().get("detail", "Unknown error") - messagebox.showerror( - "Error", f"Failed to update order: {error_msg}" + CTkMessagebox( + title="Error", + message=f"Failed to update order: {error_msg}", + icon="cancel", ) except Exception as e: - messagebox.showerror("Error", f"Failed to update order: {str(e)}") + CTkMessagebox( + title="Error", + message=f"Failed to update order: {str(e)}", + icon="cancel", + ) received_btn = ctk.CTkButton( button_frame, diff --git a/app/frontend/components/user_payments.py b/app/frontend/components/user_payments.py index fbe48d481e55126abe8425548a2f83fbdeaf9224..825f4c2acd1fa8033b6175a6604effdd7239fe32 100644 --- a/app/frontend/components/user_payments.py +++ b/app/frontend/components/user_payments.py @@ -1,6 +1,6 @@ import customtkinter as ctk import requests -from tkinter import messagebox +from CTkMessagebox import CTkMessagebox from datetime import datetime from utils.api_requests import add_payment_method @@ -430,15 +430,24 @@ def user_payments_frame(parent, switch_func, API_URL, token): } create_payment_card(temp_payment) except Exception as e: - messagebox.showerror("Error", f"Failed to load payment data: {str(e)}") + print(f"Error loading payment data: {str(e)}") + CTkMessagebox( + title="Error", message="Failed to load payment data.", icon="cancel" + ) def delete_payment(payment): """Delete payment method and update UI""" nonlocal saved_card_frame - if messagebox.askyesno( - "Confirm Delete", "Are you sure you want to delete this payment method?" - ): + result = CTkMessagebox( + title="Confirm Delete", + message="Are you sure you want to delete this payment method?", + icon="question", + option_1="Yes", + option_2="No", + ).get() + + if result == "Yes": try: headers = {"Authorization": f"Bearer {frame.token}"} response = requests.delete( @@ -462,8 +471,10 @@ def user_payments_frame(parent, switch_func, API_URL, token): if hasattr(frame, "current_payment_id"): delattr(frame, "current_payment_id") - messagebox.showinfo( - "Success", "Payment method deleted successfully" + CTkMessagebox( + title="Success", + message="Payment method deleted successfully", + icon="check", ) # Enable form inputs after successful delete @@ -475,10 +486,12 @@ def user_payments_frame(parent, switch_func, API_URL, token): error_msg = response.json().get( "detail", "Failed to delete payment method" ) - messagebox.showerror("Error", error_msg) + CTkMessagebox(title="Error", message=error_msg, icon="cancel") except Exception as e: - messagebox.showerror( - "Error", f"Failed to delete payment method: {str(e)}" + CTkMessagebox( + title="Error", + message=f"Failed to delete payment method: {str(e)}", + icon="cancel", ) def refresh_payments(): @@ -517,9 +530,10 @@ def user_payments_frame(parent, switch_func, API_URL, token): elif response.status_code == 401: # Handle unauthorized error - likely token expired print(f"Authentication error: {response.text}") - messagebox.showerror( - "Authentication Error", - "Your session has expired. Please log in again.", + CTkMessagebox( + title="Authentication Error", + message="Your session has expired. Please log in again.", + icon="cancel", ) elif response.status_code == 404: # Handle 404 gracefully - just means no payment methods yet @@ -530,8 +544,10 @@ def user_payments_frame(parent, switch_func, API_URL, token): # Handle other errors error_msg = response.json().get("detail", "Unknown error") print(f"Error refreshing payments: {error_msg}") - messagebox.showerror( - "Error", f"Failed to fetch payment methods: {error_msg}" + CTkMessagebox( + title="Error", + message=f"Failed to fetch payment methods: {error_msg}", + icon="cancel", ) except Exception as e: # Enable the form anyway so user can add payment @@ -710,7 +726,9 @@ def user_payments_frame(parent, switch_func, API_URL, token): year_entry.delete(2, "end") # Only validate the range if we have 2 digits if len(value) == 2 and (int(value) < 25 or int(value) > 99): - messagebox.showerror("Error", "Please input the correct year") + CTkMessagebox( + title="Error", message="Please input the correct year", icon="cancel" + ) year_entry.delete(0, "end") def validate_cvv(event=None): @@ -788,23 +806,25 @@ def user_payments_frame(parent, switch_func, API_URL, token): card_type = card_type_var.get().lower() if not all([card_num, month, year, cvv]): - messagebox.showerror("Error", "Please fill in all fields") + CTkMessagebox( + title="Error", message="Please fill in all fields", icon="cancel" + ) return if len(card_num) < 12: - messagebox.showerror("Error", "Invalid card number") + CTkMessagebox(title="Error", message="Invalid card number", icon="cancel") return if not (1 <= int(month) <= 12): - messagebox.showerror("Error", "Invalid month") + CTkMessagebox(title="Error", message="Invalid month", icon="cancel") return if not (25 <= int(year) <= 99): - messagebox.showerror("Error", "Invalid year") + CTkMessagebox(title="Error", message="Invalid year", icon="cancel") return if len(cvv) != 3: - messagebox.showerror("Error", "Invalid security code") + CTkMessagebox(title="Error", message="Invalid security code", icon="cancel") return # Create payload @@ -834,7 +854,11 @@ def user_payments_frame(parent, switch_func, API_URL, token): action = "added" if response.status_code == 200: - messagebox.showinfo("Success", f"Payment method {action} successfully!") + CTkMessagebox( + title="Success", + message=f"Payment method {action} successfully!", + icon="check", + ) # Clear form and reset state clear_form() # Remove the current_payment_id if it exists @@ -848,9 +872,13 @@ def user_payments_frame(parent, switch_func, API_URL, token): error_msg = response.json().get( "detail", f"Failed to {action} payment method" ) - messagebox.showerror("Error", error_msg) + CTkMessagebox(title="Error", message=error_msg, icon="cancel") except Exception as e: - messagebox.showerror("Error", f"Failed to save payment method: {str(e)}") + CTkMessagebox( + title="Error", + message=f"Failed to save payment method: {str(e)}", + icon="cancel", + ) # Save Button save_button = ctk.CTkButton( diff --git a/app/frontend/utils/api_requests.py b/app/frontend/utils/api_requests.py index 098bedafde449b93e6e727ca0be6c47feede2abd..76f2e6df7cc52d603edfd6f5fc06f68d951c8441 100644 --- a/app/frontend/utils/api_requests.py +++ b/app/frontend/utils/api_requests.py @@ -45,10 +45,16 @@ def register_api(username, email, phone_number, password, api_url): "password": password, }, ) - response.raise_for_status() # Raise an error for HTTP errors (4xx, 5xx) - return response.status_code, response.json() # Return response data + return ( + response.status_code, + response.json(), + ) # Return response data and status code + except requests.exceptions.HTTPError as e: + # Handle HTTP errors (4xx, 5xx) + return e.response.status_code, e.response.json() except requests.exceptions.RequestException as e: - return None, {"detail": str(e)} # Return error message if request fails + # Handle other request exceptions (connection errors, timeouts, etc.) + return 500, {"detail": str(e)} # Return 500 error code for request failures # Create Shop