diff --git a/app/frontend/components/product/view_product.py b/app/frontend/components/product/view_product.py index 0e84236e8e5573e69786bacef02256116106179a..c91f4a4c75bc7bb206bcf507cd93313693221579 100644 --- a/app/frontend/components/product/view_product.py +++ b/app/frontend/components/product/view_product.py @@ -195,6 +195,19 @@ def view_product_frame(parent, switch_func, API_URL, token, product_data: Dict[A """ frame = ctk.CTkFrame(parent, fg_color=DARK_BG) + # Store token as a frame attribute so it can be accessed by add_to_cart + frame.token = token + + def update_token(new_token): + """Update the token when it changes""" + nonlocal token + token = new_token + frame.token = new_token + print(f"Product view token updated: {new_token}") # Debug print + + # Add update_token method to frame + frame.update_token = update_token + # ------------- LEFT SIDEBAR ------------- sidebar = ctk.CTkFrame(frame, fg_color=CARD_BG, width=60) sidebar.pack(side="left", fill="y") @@ -432,14 +445,29 @@ def view_product_frame(parent, switch_func, API_URL, token, product_data: Dict[A messagebox.showwarning("Warning", "Please select a valid quantity (1-99).") return + # Get the current token from frame + current_token = frame.token + + # Validate token + if not current_token: + messagebox.showerror("Error", "Please log in to add items to cart") + return + # Prepare data for API call - headers = {"Authorization": f"Bearer {token}"} + headers = {"Authorization": f"Bearer {current_token}"} data = { + "shop_id": product_data.get("shop_id"), "product_id": product_data.get("id"), - "quantity": quantity + "quantity": quantity, + "price": product_data.get("price", 0) } - # Make API call + # Debug print + print(f"Token: {current_token}") + print(f"Headers: {headers}") + print(f"Data: {data}") + + # Make API call to add to cart response = requests.post(f"{API_URL}/cart/add", headers=headers, json=data) if response.status_code == 200: @@ -449,11 +477,16 @@ def view_product_frame(parent, switch_func, API_URL, token, product_data: Dict[A else: error_msg = response.json().get("detail", "Unknown error occurred") messagebox.showerror("Error", f"Failed to add to cart: {error_msg}") + # Debug print + print(f"Response status: {response.status_code}") + print(f"Response body: {response.text}") except requests.RequestException as e: messagebox.showerror("Error", f"Network error: {str(e)}") except Exception as e: messagebox.showerror("Error", f"An unexpected error occurred: {str(e)}") + # Debug print + print(f"Exception: {str(e)}") # Add to cart button add_to_cart_btn = ctk.CTkButton( diff --git a/app/frontend/components/shop/create_shop.py b/app/frontend/components/shop/create_shop.py index 6d5a991bb4b207be811c2b820be9725523b8b97b..ace896353cabf8867422a405cebd45372c35069a 100644 --- a/app/frontend/components/shop/create_shop.py +++ b/app/frontend/components/shop/create_shop.py @@ -15,6 +15,12 @@ def create_shop_frame(parent, switch_func, API_URL, token): # Main container frame frame = ctk.CTkFrame(parent, fg_color=DARK_BG) + def update_token(new_token): + nonlocal token + token = new_token + + frame.update_token = update_token # Add the method to the frame + # --- TOP BAR --- top_bar = ctk.CTkFrame(frame, fg_color=SHOPPING, height=50) top_bar.pack(fill="x", side="top") @@ -209,14 +215,20 @@ def create_shop_frame(parent, switch_func, API_URL, token): return headers = {"Authorization": f"Bearer {token}"} - payload = { - "name": shop_name, - "description": shop_description, - "address": shop_address, + + # Create form data + form_data = { + "name": (None, shop_name), + "description": (None, shop_description), + "address": (None, shop_address), } try: - resp = requests.post(f"{API_URL}/shops/create", headers=headers, json=payload) + resp = requests.post( + f"{API_URL}/shops/create", + headers=headers, + files=form_data # Use files parameter for form data + ) if resp.status_code == 200: messagebox.showinfo("Success", "Shop created successfully!") switch_func("dashboard") diff --git a/app/frontend/components/user_orders.py b/app/frontend/components/user_orders.py index f112f31b63c8da40c8f0ca8a000ade6f6aaa3a72..8e694e457ce3907c84f704d8d83e2219437ae01f 100644 --- a/app/frontend/components/user_orders.py +++ b/app/frontend/components/user_orders.py @@ -7,7 +7,15 @@ import io SHOPPING = "#00c1ff" DARK_BG = "#1f1f1f" CARD_BG = "#2b2b2b" +BACKEND_HOST = "http://127.0.0.1:8000" +def fix_url(url): + if url.startswith("http"): + return url + prefix = "app/static/" + if url.startswith(prefix): + url = url[len(prefix):] + return f"{BACKEND_HOST}/static/{url}" def user_orders_frame(parent, switch_func, API_URL, token): """ @@ -17,7 +25,7 @@ def user_orders_frame(parent, switch_func, API_URL, token): # Main container frame with dark background frame = ctk.CTkFrame(parent, fg_color=DARK_BG) - # ----------------- TOP BAR ----------------- + # --- TOP BAR --- top_bar = ctk.CTkFrame(frame, fg_color=SHOPPING, height=50) top_bar.pack(fill="x", side="top") @@ -45,11 +53,11 @@ def user_orders_frame(parent, switch_func, API_URL, token): ) back_button.pack(side="right", padx=20, pady=7) - # ----------------- MAIN SECTION (Sidebar + Content) ----------------- + # --- MAIN SECTION: Sidebar + Content --- main_section = ctk.CTkFrame(frame, fg_color="transparent") main_section.pack(fill="both", expand=True) - # ----------------- LEFT SIDEBAR ----------------- + # LEFT SIDEBAR sidebar_frame = ctk.CTkFrame(main_section, width=250, fg_color=CARD_BG, corner_radius=15) sidebar_frame.pack(side="left", fill="y", padx=20, pady=20) sidebar_frame.pack_propagate(False) @@ -93,188 +101,164 @@ def user_orders_frame(parent, switch_func, API_URL, token): nav_payments = create_nav_button("My Payments", open_payments) nav_payments.pack(fill="x", padx=15, pady=5) - # ----------------- RIGHT CONTENT (Orders List) ----------------- + become_owner = create_nav_button("Become Shop Owner", lambda: switch_func("create_shop")) + become_owner.pack(fill="x", padx=15, pady=5) + + # RIGHT CONTENT content_frame = ctk.CTkFrame(main_section, fg_color=CARD_BG, corner_radius=15) content_frame.pack(side="left", fill="both", expand=True, padx=(0, 20), pady=20) - # Header - header_frame = ctk.CTkFrame(content_frame, fg_color="transparent") - header_frame.pack(fill="x", padx=30, pady=20) + # ORDER HISTORY + orders_frame = ctk.CTkScrollableFrame(content_frame, fg_color="transparent") + orders_frame.pack(fill="both", expand=True, padx=20, pady=20) - main_title_label = ctk.CTkLabel( - header_frame, - text="Order History", - font=("Helvetica", 24, "bold"), + # Header for orders + header_label = ctk.CTkLabel( + orders_frame, + text="Your Order History", + font=("Helvetica", 18, "bold"), text_color=SHOPPING ) - main_title_label.pack(anchor="w") - - subtitle_label = ctk.CTkLabel( - header_frame, - text="Review the products you have purchased", - font=("Helvetica", 14), - text_color="gray" - ) - subtitle_label.pack(anchor="w", pady=(5, 0)) - - # Orders List Container - orders_list_frame = ctk.CTkScrollableFrame( - content_frame, - fg_color="transparent", - corner_radius=0 - ) - orders_list_frame.pack(fill="both", expand=True, padx=30, pady=(0, 20)) + header_label.pack(anchor="w", pady=(0, 20)) - def create_order_card(order_data): - product = order_data.get("product", {}) - order_date = order_data.get("order_date", "Unknown Date") - - card = ctk.CTkFrame(orders_list_frame, fg_color="#3b3b3b", corner_radius=10) - card.pack(fill="x", pady=5) - - # Left: Product image - image_frame = ctk.CTkFrame(card, fg_color="transparent", width=100, height=100) - image_frame.pack(side="left", padx=15, pady=15) - image_frame.pack_propagate(False) - - image_label = ctk.CTkLabel(image_frame, text="") - image_label.pack(expand=True) + def load_order_history(): + # Clear existing items + for widget in orders_frame.winfo_children()[1:]: # Keep the header + widget.destroy() - if product.get("images"): - try: - img_url = product["images"][0]["image_url"] - img_resp = requests.get(img_url) - if img_resp.status_code == 200: - pil_img = Image.open(io.BytesIO(img_resp.content)).resize((80, 80)) - tk_img = ImageTk.PhotoImage(pil_img) - image_label.configure(image=tk_img, text="") - image_label.image = tk_img - except Exception as ex: - print(f"Product image error: {ex}") + try: + headers = {"Authorization": f"Bearer {token}"} + response = requests.get(f"{API_URL}/orders/list", headers=headers) + + if response.status_code == 200: + orders = response.json() + + if not orders: + empty_label = ctk.CTkLabel( + orders_frame, + text="You haven't placed any orders yet", + font=("Helvetica", 14) + ) + empty_label.pack(pady=20) + return - # Right: Order details - info_frame = ctk.CTkFrame(card, fg_color="transparent") - info_frame.pack(side="left", fill="both", expand=True, padx=15, pady=15) + for order in orders: + order_frame = create_order_history_frame(order) + order_frame.pack(fill="x", pady=10) - # Product Name + else: + error_msg = response.json().get("detail", "Unknown error") + messagebox.showerror("Error", f"Failed to load orders: {error_msg}") + + except Exception as e: + messagebox.showerror("Error", f"Failed to load order history: {str(e)}") + + def create_order_history_frame(order): + order_frame = ctk.CTkFrame(orders_frame, fg_color=CARD_BG, corner_radius=10) + + # Order header + header_frame = ctk.CTkFrame(order_frame, fg_color="#212121", corner_radius=5) + header_frame.pack(fill="x", padx=10, pady=(10, 0)) + + # Order ID and date + order_id = order.get("id", "Unknown") + created_at = order.get("created_at", "Unknown date") + if isinstance(created_at, str) and len(created_at) > 10: + created_at = created_at[:10] # Just get the date part + + header_text = f"Order #{order_id} - {created_at}" ctk.CTkLabel( - info_frame, - text=product.get("name", "No Name"), - font=("Helvetica", 16, "bold"), - text_color="white" - ).pack(anchor="w") - - # Price and Date - price = product.get("price", 0.0) + header_frame, + text=header_text, + font=("Helvetica", 14, "bold") + ).pack(side="left", padx=10, pady=5) + + # Order status + status = order.get("status", "unknown").upper() + status_colors = { + "PENDING": "#FFA500", + "PROCESSING": "#4169E1", + "SHIPPED": "#9370DB", + "DELIVERED": "#32CD32", + "COMPLETED": "#32CD32", + "CANCELLED": "#DC143C" + } + status_color = status_colors.get(status, "#808080") + ctk.CTkLabel( - info_frame, - text=f"₫ {price:,.1f}", - font=("Helvetica", 14), - text_color=SHOPPING - ).pack(anchor="w", pady=(5, 0)) - + header_frame, + text=status, + font=("Helvetica", 14, "bold"), + text_color=status_color + ).pack(side="right", padx=10, pady=5) + + # Order details + details_frame = ctk.CTkFrame(order_frame, fg_color="transparent") + details_frame.pack(fill="x", padx=10, pady=10) + + # Delivery address + address_frame = ctk.CTkFrame(details_frame, fg_color="transparent") + address_frame.pack(fill="x", pady=5) + ctk.CTkLabel( - info_frame, - text=f"Ordered on: {order_date}", - font=("Helvetica", 12), - text_color="gray" - ).pack(anchor="w", pady=(5, 0)) - - # Shop Name - shop_id = product.get("shop_id") - shop_name_label = ctk.CTkLabel( - info_frame, - text="Shop: Loading...", + address_frame, + text="Delivery Address:", + font=("Helvetica", 12, "bold"), + text_color="#AAAAAA" + ).pack(side="left", padx=(10, 5)) + + ctk.CTkLabel( + address_frame, + text=order.get("delivery_address", "No address provided"), font=("Helvetica", 12), - text_color="gray" - ) - shop_name_label.pack(anchor="w", pady=(5, 0)) - - def fetch_shop_and_update_label(sid, label_widget): - headers = {"Authorization": f"Bearer {token}"} - try: - sresp = requests.get(f"{API_URL}/shop/get/{sid}", headers=headers) - if sresp.status_code == 200: - shop_data = sresp.json() - label_widget.configure( - text=f"Shop: {shop_data.get('name', 'No Shop Name')}" - ) - else: - label_widget.configure(text="Shop: Not found") - except Exception: - label_widget.configure(text="Shop: Error fetching") - - fetch_shop_and_update_label(shop_id, shop_name_label) - - # View Order Button - def view_order(): - messagebox.showinfo( - "Order Details", - f"View details for order of {product.get('name')}" - ) - - view_button = ctk.CTkButton( - info_frame, - text="View Order", - command=view_order, - corner_radius=8, - height=35, + text_color="white" + ).pack(side="left") + + # Totals + totals_frame = ctk.CTkFrame(details_frame, fg_color="transparent") + totals_frame.pack(fill="x", pady=5) + + # Shipping price + shipping_frame = ctk.CTkFrame(totals_frame, fg_color="transparent") + shipping_frame.pack(fill="x") + + ctk.CTkLabel( + shipping_frame, + text="Shipping:", + font=("Helvetica", 12, "bold"), + text_color="#AAAAAA" + ).pack(side="left", padx=(10, 5)) + + shipping_price = order.get("shipping_price", 0) + ctk.CTkLabel( + shipping_frame, + text=f"₫{shipping_price:,.0f}", font=("Helvetica", 12), - fg_color=SHOPPING, - hover_color="#0096ff", - ) - view_button.pack(anchor="e", pady=(10, 0)) - - def fetch_orders(): - """ - Fetch the list of user orders from the API. - """ - headers = {"Authorization": f"Bearer {token}"} - try: - resp = requests.get(f"{API_URL}/orders/list", headers=headers) - if resp.status_code == 200: - orders = resp.json() - display_orders(orders) - else: - messagebox.showerror("Error", "Failed to fetch orders.") - except Exception as ex: - messagebox.showerror("Error", f"Request error: {ex}") - - def display_orders(orders): - """ - Display each order with product and shop details. - """ - # Clear previous content - for widget in orders_list_frame.winfo_children(): - widget.destroy() - - if not orders: - no_orders_frame = ctk.CTkFrame(orders_list_frame, fg_color="transparent") - no_orders_frame.pack(expand=True, pady=20) - - ctk.CTkLabel( - no_orders_frame, - text="No orders found", - font=("Helvetica", 16), - text_color="gray" - ).pack() - - ctk.CTkButton( - no_orders_frame, - text="Start Shopping", - command=go_back, - corner_radius=8, - height=40, - font=("Helvetica", 14), - fg_color=SHOPPING, - hover_color="#0096ff", - ).pack(pady=(20, 0)) - return - - for order in orders: - create_order_card(order) - - # Fetch orders when the frame loads - fetch_orders() + text_color="white" + ).pack(side="left") + + # Total price + total_frame = ctk.CTkFrame(totals_frame, fg_color="transparent") + total_frame.pack(fill="x") + + ctk.CTkLabel( + total_frame, + text="Total:", + font=("Helvetica", 14, "bold"), + text_color="#AAAAAA" + ).pack(side="left", padx=(10, 5)) + + total_price = order.get("total_price", 0) + ctk.CTkLabel( + total_frame, + text=f"₫{total_price:,.0f}", + font=("Helvetica", 14, "bold"), + text_color=SHOPPING + ).pack(side="left") + + return order_frame + # Load orders when the frame is created + load_order_history() + return frame diff --git a/app/frontend/components/user_payments.py b/app/frontend/components/user_payments.py index 0573ad380b4ac90729b5a79b3d018932fdb7d432..95740a14b4d72bbc0f39b6849948a5bd7e4f9603 100644 --- a/app/frontend/components/user_payments.py +++ b/app/frontend/components/user_payments.py @@ -129,12 +129,26 @@ def user_payments_frame(parent, switch_func, API_URL, token): ) subtitle_label.pack(anchor="w", pady=(5, 0)) - # Main Content - main_content = ctk.CTkFrame(content_scrollable, fg_color="transparent") - main_content.pack(fill="both", expand=True, padx=30, pady=(0, 20)) + # Create two columns container + columns_container = ctk.CTkFrame(content_scrollable, fg_color="transparent") + columns_container.pack(fill="both", expand=True, padx=30, pady=(0, 20)) + columns_container.grid_columnconfigure(0, weight=1) + columns_container.grid_columnconfigure(1, weight=1) + + # Left Column - Form and Preview + left_column = ctk.CTkFrame(columns_container, fg_color="transparent") + left_column.grid(row=0, column=0, sticky="nsew", padx=(0, 15)) + + # Right Column - Current Payment Method + right_column = ctk.CTkFrame(columns_container, fg_color="transparent") + right_column.grid(row=0, column=1, sticky="nsew", padx=(15, 0)) + + # Form Frame in Left Column + form_frame = ctk.CTkFrame(left_column, fg_color="transparent") + form_frame.pack(fill="x", pady=(0, 20)) - # Saved Payment Methods Section - saved_payments_frame = ctk.CTkFrame(main_content, fg_color="transparent") + # Saved Payment Methods Section in Right Column + saved_payments_frame = ctk.CTkFrame(right_column, fg_color="transparent") saved_payments_frame.pack(fill="x", pady=(0, 40)) saved_payments_label = ctk.CTkLabel( @@ -433,14 +447,24 @@ def user_payments_frame(parent, switch_func, API_URL, token): # If no payment methods, clear everything and enable form clear_form() enable_form_inputs() + elif response.status_code == 401: + # Handle unauthorized error - likely token expired + messagebox.showerror("Authentication Error", "Your session has expired. Please log in again.") + elif response.status_code == 404: + # Handle 404 gracefully - just means no payment methods yet + clear_form() + enable_form_inputs() else: - messagebox.showerror("Error", "Failed to fetch payment method") + # Handle other errors + error_msg = response.json().get("detail", "Unknown error") + messagebox.showerror("Error", f"Failed to fetch payment methods: {error_msg}") except Exception as e: - messagebox.showerror("Error", f"Failed to fetch payment method: {str(e)}") - - # Payment Form - Move this section up before it's used - form_frame = ctk.CTkFrame(main_content, fg_color="transparent") - form_frame.pack(fill="x", pady=(0, 20)) + # Enable the form anyway so user can add payment + clear_form() + enable_form_inputs() + # Show a less alarming message + print(f"Payment fetch error: {str(e)}") + # Don't show error to user, just let them add a payment # Card Number Field card_label = ctk.CTkLabel( diff --git a/app/frontend/utils/api_requests.py b/app/frontend/utils/api_requests.py index 95d56892e2e98abe225e953cff44bc269d42a270..493ad18611594078bdf9a64ddeab2ed548a8075d 100644 --- a/app/frontend/utils/api_requests.py +++ b/app/frontend/utils/api_requests.py @@ -172,3 +172,64 @@ def add_payment_method(api_url, token, payment_data): return response.status_code, response.json() except requests.exceptions.RequestException as e: return None, {"detail": str(e)} + + +# Profile Management APIs +def get_user_profile(api_url, token): + """ + Fetches user profile information. + + :param api_url: Base API URL + :param token: User's access token + :return: Tuple (status_code, response_data) + """ + if not token: + print("Debug - Token is missing in get_user_profile") # Debug print + return 401, {"detail": "Access token not found. Please log in."} + + headers = { + "Authorization": f"Bearer {token}", + "Content-Type": "application/json" + } + print(f"Debug - Making profile request with token: {token[:10]}...") # Debug print + + try: + response = requests.get( + f"{api_url}/auth/profile", + headers=headers + ) + return response.status_code, response.json() + except requests.exceptions.RequestException as e: + print(f"Debug - Request error in get_user_profile: {str(e)}") # Debug print + return None, {"detail": str(e)} + +def update_user_profile(api_url, token, profile_data): + """ + Updates user profile information. + + :param api_url: Base API URL + :param token: User's access token + :param profile_data: Dictionary containing fields to update (username, email, phone_number) + :return: Tuple (status_code, response_data) + """ + if not token: + print("Debug - Token is missing in update_user_profile") # Debug print + return 401, {"detail": "Access token not found. Please log in."} + + headers = { + "Authorization": f"Bearer {token}", + "Content-Type": "application/json" + } + print(f"Debug - Making update request with token: {token[:10]}...") # Debug print + print(f"Debug - Update payload: {profile_data}") # Debug print + + try: + response = requests.put( + f"{api_url}/auth/profile", + headers=headers, + json=profile_data + ) + return response.status_code, response.json() + except requests.exceptions.RequestException as e: + print(f"Debug - Request error in update_user_profile: {str(e)}") # Debug print + return None, {"detail": str(e)}