diff --git a/app/backend/routes/product.py b/app/backend/routes/product.py index 30ca0e9f308fcd5d627ec95dbcaa7db6407bba75..25a36846289cee015f08df886161a1de4888d1c3 100644 --- a/app/backend/routes/product.py +++ b/app/backend/routes/product.py @@ -13,6 +13,7 @@ router = APIRouter() BACKEND_HOST = "http://127.0.0.1:8000" # Or use settings.backend_url if available + @router.post("/create", response_model=ProductRead) def create_product( name: str = Form(...), @@ -59,12 +60,16 @@ def create_product( relative_path = os.path.relpath(file_location, settings.static_dir) relative_path = relative_path.replace("\\", "/") public_url = f"{BACKEND_HOST}/static/{relative_path}" - product_image = ProductImage(product_id=product.id, image_url=public_url) + product_image = ProductImage( + product_id=product.id, image_url=public_url + ) session.add(product_image) session.commit() else: # Use default product image from static/default folder - default_path = os.path.join(settings.static_dir, "default", "default_product.png") + default_path = os.path.join( + settings.static_dir, "default", "default_product.png" + ) relative_path = os.path.relpath(default_path, settings.static_dir) relative_path = relative_path.replace("\\", "/") public_url = f"{BACKEND_HOST}/static/{relative_path}" diff --git a/app/backend/routes/shop.py b/app/backend/routes/shop.py index a545b83a89e75bcaa62f9395aed67a0caa0dc325..ed54658f66babe1c31f2a417e22f75edc4d6ac6b 100644 --- a/app/backend/routes/shop.py +++ b/app/backend/routes/shop.py @@ -14,6 +14,7 @@ router = APIRouter() # Define your backend host URL. BACKEND_HOST = "http://127.0.0.1:8000" # Or use settings.backend_url if available + @router.post("/create", response_model=ShopRead) def create_shop( name: str = Form(...), @@ -86,14 +87,11 @@ def read_shop(shop_id: int, session: Session = Depends(get_session)): shop = session.get(Shop, shop_id) if not shop: raise HTTPException(status_code=404, detail="Shop not found") - + # Query products related to the shop products = session.query(Product).filter(Product.shop_id == shop_id).all() - - return { - "shop": shop, - "products": products - } + + return {"shop": shop, "products": products} @router.put("/put/{shop_id}", response_model=ShopRead) @@ -111,7 +109,7 @@ def update_shop( raise HTTPException(status_code=404, detail="Shop not found") if shop.owner_id != current_user.id: raise HTTPException(status_code=403, detail="Unauthorized to update this shop") - + if name: shop.name = name if description: diff --git a/app/frontend/components/auth/login.py b/app/frontend/components/auth/login.py index f44c06fbb19d8b6b4ead29df0d0c96c3c75ea2b1..18776faec952762073f3831175b92a7cf9d06862 100644 --- a/app/frontend/components/auth/login.py +++ b/app/frontend/components/auth/login.py @@ -8,6 +8,7 @@ SHOPPING = "#00c1ff" DARK_BG = "#1f1f1f" CARD_BG = "#2b2b2b" + def login_frame(parent, switch_func, API_URL): # Overall container with dark background container = ctk.CTkFrame(parent, fg_color=DARK_BG) @@ -27,7 +28,7 @@ def login_frame(parent, switch_func, API_URL): brand_frame, text="Shopping App", font=("Helvetica", 40, "bold"), - text_color=SHOPPING + text_color=SHOPPING, ) app_name.pack() @@ -35,7 +36,7 @@ def login_frame(parent, switch_func, API_URL): brand_frame, text="Your One-Stop Shopping Destination", font=("Helvetica", 16), - text_color="white" + text_color="white", ) app_slogan.pack(pady=(10, 30)) @@ -73,14 +74,14 @@ def login_frame(parent, switch_func, API_URL): form_frame, text="Welcome Back", font=("Helvetica", 32, "bold"), - text_color=SHOPPING + text_color=SHOPPING, ).pack(pady=(0, 10)) ctk.CTkLabel( form_frame, text="Sign in to your account", font=("Helvetica", 14), - text_color="gray" + text_color="gray", ).pack(pady=(0, 20)) # Email @@ -88,10 +89,7 @@ def login_frame(parent, switch_func, API_URL): email_frame.pack(fill="x", pady=(0, 15)) ctk.CTkLabel( - email_frame, - text="Email", - font=("Helvetica", 14), - text_color="white" + email_frame, text="Email", font=("Helvetica", 14), text_color="white" ).pack(anchor="w") entry_email = ctk.CTkEntry( @@ -101,7 +99,7 @@ def login_frame(parent, switch_func, API_URL): placeholder_text="Enter your email", fg_color="#3b3b3b", border_color=SHOPPING, - text_color="white" + text_color="white", ) entry_email.pack(fill="x", pady=(5, 0)) @@ -110,10 +108,7 @@ def login_frame(parent, switch_func, API_URL): password_frame.pack(fill="x", pady=(0, 20)) ctk.CTkLabel( - password_frame, - text="Password", - font=("Helvetica", 14), - text_color="white" + password_frame, text="Password", font=("Helvetica", 14), text_color="white" ).pack(anchor="w") entry_password = ctk.CTkEntry( @@ -124,7 +119,7 @@ def login_frame(parent, switch_func, API_URL): placeholder_text="Enter your password", fg_color="#3b3b3b", border_color=SHOPPING, - text_color="white" + text_color="white", ) entry_password.pack(fill="x", pady=(5, 0)) @@ -148,7 +143,7 @@ def login_frame(parent, switch_func, API_URL): register_frame, text="Don't have an account?", font=("Helvetica", 12), - text_color="gray" + text_color="gray", ).pack(side="left", padx=(0, 5)) ctk.CTkButton( @@ -160,7 +155,7 @@ def login_frame(parent, switch_func, API_URL): text_color=SHOPPING, font=("Helvetica", 12, "bold"), width=70, - height=25 + height=25, ).pack(side="left") return container diff --git a/app/frontend/components/auth/register.py b/app/frontend/components/auth/register.py index 6677b2fbd966dfb6113bc4add55f5b6806ae5e86..dd6419923c1046334918861a596f1b02bcfcd979 100644 --- a/app/frontend/components/auth/register.py +++ b/app/frontend/components/auth/register.py @@ -6,6 +6,7 @@ SHOPPING = "#00c1ff" DARK_BG = "#1f1f1f" CARD_BG = "#2b2b2b" + def register_frame(parent, switch_func, API_URL): # Create a full-window container with dark background container = ctk.CTkFrame(parent, fg_color=DARK_BG) @@ -25,7 +26,7 @@ def register_frame(parent, switch_func, API_URL): brand_frame, text="Shopping App", font=("Helvetica", 40, "bold"), - text_color=SHOPPING + text_color=SHOPPING, ) app_name.pack() @@ -33,7 +34,7 @@ def register_frame(parent, switch_func, API_URL): brand_frame, text="Join Our Shopping Community", font=("Helvetica", 16), - text_color="white" + text_color="white", ) app_slogan.pack(pady=(10, 30)) @@ -77,14 +78,14 @@ def register_frame(parent, switch_func, API_URL): form_frame, text="Create Account", font=("Helvetica", 32, "bold"), - text_color=SHOPPING + text_color=SHOPPING, ).pack(pady=(0, 10)) ctk.CTkLabel( form_frame, text="Fill in your details to register", font=("Helvetica", 14), - text_color="gray" + text_color="gray", ).pack(pady=(0, 20)) # Username @@ -92,10 +93,7 @@ def register_frame(parent, switch_func, API_URL): username_frame.pack(fill="x", pady=(0, 15)) ctk.CTkLabel( - username_frame, - text="Username", - font=("Helvetica", 14), - text_color="white" + username_frame, text="Username", font=("Helvetica", 14), text_color="white" ).pack(anchor="w") entry_username = ctk.CTkEntry( @@ -105,7 +103,7 @@ def register_frame(parent, switch_func, API_URL): placeholder_text="Enter your username", fg_color="#3b3b3b", border_color=SHOPPING, - text_color="white" + text_color="white", ) entry_username.pack(fill="x", pady=(5, 0)) @@ -114,10 +112,7 @@ def register_frame(parent, switch_func, API_URL): email_frame.pack(fill="x", pady=(0, 15)) ctk.CTkLabel( - email_frame, - text="Email", - font=("Helvetica", 14), - text_color="white" + email_frame, text="Email", font=("Helvetica", 14), text_color="white" ).pack(anchor="w") entry_email = ctk.CTkEntry( @@ -127,7 +122,7 @@ def register_frame(parent, switch_func, API_URL): placeholder_text="Enter your email", fg_color="#3b3b3b", border_color=SHOPPING, - text_color="white" + text_color="white", ) entry_email.pack(fill="x", pady=(5, 0)) @@ -136,10 +131,7 @@ def register_frame(parent, switch_func, API_URL): phone_frame.pack(fill="x", pady=(0, 15)) ctk.CTkLabel( - phone_frame, - text="Phone Number", - font=("Helvetica", 14), - text_color="white" + phone_frame, text="Phone Number", font=("Helvetica", 14), text_color="white" ).pack(anchor="w") entry_phone = ctk.CTkEntry( @@ -149,7 +141,7 @@ def register_frame(parent, switch_func, API_URL): placeholder_text="Enter your phone number", fg_color="#3b3b3b", border_color=SHOPPING, - text_color="white" + text_color="white", ) entry_phone.pack(fill="x", pady=(5, 0)) @@ -158,10 +150,7 @@ def register_frame(parent, switch_func, API_URL): password_frame.pack(fill="x", pady=(0, 15)) ctk.CTkLabel( - password_frame, - text="Password", - font=("Helvetica", 14), - text_color="white" + password_frame, text="Password", font=("Helvetica", 14), text_color="white" ).pack(anchor="w") entry_password = ctk.CTkEntry( @@ -172,7 +161,7 @@ def register_frame(parent, switch_func, API_URL): placeholder_text="Enter your password", fg_color="#3b3b3b", border_color=SHOPPING, - text_color="white" + text_color="white", ) entry_password.pack(fill="x", pady=(5, 0)) @@ -184,7 +173,7 @@ def register_frame(parent, switch_func, API_URL): confirm_frame, text="Confirm Password", font=("Helvetica", 14), - text_color="white" + text_color="white", ).pack(anchor="w") entry_confirm_password = ctk.CTkEntry( @@ -195,7 +184,7 @@ def register_frame(parent, switch_func, API_URL): placeholder_text="Confirm your password", fg_color="#3b3b3b", border_color=SHOPPING, - text_color="white" + text_color="white", ) entry_confirm_password.pack(fill="x", pady=(5, 0)) @@ -219,7 +208,7 @@ def register_frame(parent, switch_func, API_URL): login_frame, text="Already have an account?", font=("Helvetica", 12), - text_color="gray" + text_color="gray", ).pack(side="left", padx=(0, 5)) ctk.CTkButton( @@ -231,7 +220,7 @@ def register_frame(parent, switch_func, API_URL): text_color=SHOPPING, font=("Helvetica", 12, "bold"), width=70, - height=25 + height=25, ).pack(side="left") return container diff --git a/app/frontend/components/dashboard.py b/app/frontend/components/dashboard.py index 00f8caf17cb3fc055e56793932fee57e572c1cf0..180c49bd07300aa760e090fe39ce6373910d4735 100644 --- a/app/frontend/components/dashboard.py +++ b/app/frontend/components/dashboard.py @@ -22,7 +22,7 @@ def fix_url(url): # If the URL starts with "app/static/", remove that part. prefix = "app/static/" if url.startswith(prefix): - url = url[len(prefix):] + url = url[len(prefix) :] # Prepend the public URL return f"{BACKEND_HOST}/static/{url}" @@ -45,7 +45,7 @@ def dashboard_frame(parent, switch_func, API_URL, token): hover_color="#3b3b3b", width=40, height=40, - command=lambda: switch_func("dashboard") + command=lambda: switch_func("dashboard"), ) dashboard_icon.pack(pady=(20, 10)) @@ -59,7 +59,7 @@ def dashboard_frame(parent, switch_func, API_URL, token): hover_color="#3b3b3b", width=40, height=40, - command=lambda: switch_func("user_details") + command=lambda: switch_func("user_details"), ) user_icon.pack(pady=10) @@ -73,7 +73,7 @@ def dashboard_frame(parent, switch_func, API_URL, token): hover_color="#3b3b3b", width=40, height=40, - command=lambda: switch_func("user_orders") + command=lambda: switch_func("user_orders"), ) cart_icon.pack(pady=10) @@ -92,7 +92,7 @@ def dashboard_frame(parent, switch_func, API_URL, token): height=35, fg_color="#3b3b3b", text_color="white", - placeholder_text_color="#888888" + placeholder_text_color="#888888", ) search_entry.pack(side="left", fill="x", expand=True, padx=(20, 10), pady=12) @@ -125,7 +125,7 @@ def dashboard_frame(parent, switch_func, API_URL, token): text_color="white", width=100, height=35, - command=perform_search + command=perform_search, ) search_button.pack(side="right", padx=20) @@ -162,22 +162,16 @@ def dashboard_frame(parent, switch_func, API_URL, token): "hover_color": SHOPPING, "text_color": SHOPPING, # Text color matches SHOPPING "border_width": 2, - "border_color": SHOPPING + "border_color": SHOPPING, } prev_button = ctk.CTkButton( - nav_buttons_frame, - text="←", - command=prev_slide, - **button_config + nav_buttons_frame, text="←", command=prev_slide, **button_config ) prev_button.pack(side="left", padx=3) next_button = ctk.CTkButton( - nav_buttons_frame, - text="→", - command=next_slide, - **button_config + nav_buttons_frame, text="→", command=next_slide, **button_config ) next_button.pack(side="left", padx=3) @@ -186,12 +180,12 @@ def dashboard_frame(parent, switch_func, API_URL, token): def create_shop_slide(shop_data): slide = ctk.CTkFrame(carousel_content, fg_color="transparent") - + # Shop Image image_frame = ctk.CTkFrame(slide, fg_color="transparent", width=400, height=200) image_frame.pack(side="left", padx=20) image_frame.pack_propagate(False) - + image_label = ctk.CTkLabel(image_frame, text="") image_label.pack(expand=True) @@ -216,7 +210,7 @@ def dashboard_frame(parent, switch_func, API_URL, token): details_frame, text=shop_data.get("name", "Shop Name"), font=("Helvetica", 24, "bold"), - text_color=SHOPPING + text_color=SHOPPING, ) shop_name.pack(anchor="w", pady=(0, 10)) @@ -226,7 +220,7 @@ def dashboard_frame(parent, switch_func, API_URL, token): font=("Helvetica", 14), text_color="white", wraplength=400, - justify="left" + justify="left", ) shop_desc.pack(anchor="w", pady=5) @@ -237,7 +231,7 @@ def dashboard_frame(parent, switch_func, API_URL, token): text_color="white", command=lambda: switch_func("view_shop", shop_data.get("id")), width=120, - height=35 + height=35, ) visit_button.pack(anchor="w", pady=20) @@ -275,10 +269,7 @@ def dashboard_frame(parent, switch_func, API_URL, token): for i in range(len(featured_shops)): color = SHOPPING if i == current_shop_index else "gray" dot = ctk.CTkLabel( - dots_frame, - text="●", - text_color=color, - font=("Helvetica", 16) + dots_frame, text="●", text_color=color, font=("Helvetica", 16) ) dot.pack(side="left", padx=2) @@ -293,24 +284,18 @@ def dashboard_frame(parent, switch_func, API_URL, token): products_header, text="Featured Products", font=("Helvetica", 20, "bold"), - text_color="white" + text_color="white", ) products_title.pack(side="left") products_frame = ctk.CTkScrollableFrame( - products_section, - fg_color="transparent", - height=400 + products_section, fg_color="transparent", height=400 ) products_frame.pack(fill="both", expand=True) def create_product_card(parent, product_data): card = ctk.CTkFrame( - parent, - fg_color=CARD_BG, - corner_radius=10, - width=200, - height=280 + parent, fg_color=CARD_BG, corner_radius=10, width=200, height=280 ) card.pack_propagate(False) @@ -330,7 +315,9 @@ def dashboard_frame(parent, switch_func, API_URL, token): try: resp = requests.get(fixed_url) if resp.status_code == 200: - pil_img = Image.open(io.BytesIO(resp.content)).resize((120, 120)) + pil_img = Image.open(io.BytesIO(resp.content)).resize( + (120, 120) + ) tk_img = ImageTk.PhotoImage(pil_img) image_label.configure(image=tk_img, text="") image_label.image = tk_img @@ -346,7 +333,7 @@ def dashboard_frame(parent, switch_func, API_URL, token): text=product_data.get("name", "No Name"), font=("Helvetica", 14, "bold"), wraplength=180, - justify="left" + justify="left", ) name_label.pack(anchor="w") @@ -354,7 +341,7 @@ def dashboard_frame(parent, switch_func, API_URL, token): details_frame, text=f"₫ {product_data.get('price', 0.0):,.1f}", font=("Helvetica", 14, "bold"), - text_color="#ff4242" + text_color="#ff4242", ) price_label.pack(anchor="w", pady=5) @@ -364,7 +351,7 @@ def dashboard_frame(parent, switch_func, API_URL, token): command=lambda: switch_func("view_product", product_data), fg_color=SHOPPING, text_color="white", - height=30 + height=30, ) view_button.pack(side="bottom", pady=5) @@ -379,7 +366,7 @@ def dashboard_frame(parent, switch_func, API_URL, token): container, text="No products available.", font=("Helvetica", 14), - text_color="gray" + text_color="gray", ) no_products_label.pack(pady=20) return @@ -394,7 +381,7 @@ def dashboard_frame(parent, switch_func, API_URL, token): for product in products: product_card = create_product_card(grid_frame, product) product_card.grid(row=row, column=col, padx=10, pady=10, sticky="nsew") - + col += 1 if col >= max_cols: col = 0 diff --git a/app/frontend/components/product/view_product.py b/app/frontend/components/product/view_product.py index c91f4a4c75bc7bb206bcf507cd93313693221579..d6d3dab66f117fcbee16d0703f70157781efc5c5 100644 --- a/app/frontend/components/product/view_product.py +++ b/app/frontend/components/product/view_product.py @@ -21,7 +21,7 @@ def fix_url(url): # If the URL starts with "app/static/", remove that part. prefix = "app/static/" if url.startswith(prefix): - url = url[len(prefix):] + url = url[len(prefix) :] # Prepend the public URL return f"{BACKEND_HOST}/static/{url}" @@ -41,7 +41,9 @@ def view_shop_frame(parent, switch_func, API_URL, token, shop_id): shop_data = data.get("shop", {}) products = data.get("products", []) else: - messagebox.showerror("Error", f"Failed to fetch shop data. Status: {response.status_code}") + messagebox.showerror( + "Error", f"Failed to fetch shop data. Status: {response.status_code}" + ) return frame except Exception as e: messagebox.showerror("Error", f"Request error: {e}") @@ -189,12 +191,15 @@ def view_shop_frame(parent, switch_func, API_URL, token, shop_id): return frame -def view_product_frame(parent, switch_func, API_URL, token, product_data: Dict[Any, Any] = None): + +def view_product_frame( + parent, switch_func, API_URL, token, product_data: Dict[Any, Any] = None +): """ Create a professional product view frame similar to modern e-commerce sites. """ 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 @@ -223,7 +228,7 @@ def view_product_frame(parent, switch_func, API_URL, token, product_data: Dict[A hover_color="#3b3b3b", width=40, height=40, - command=lambda: switch_func("dashboard") + command=lambda: switch_func("dashboard"), ) dashboard_icon.pack(pady=(20, 10)) @@ -237,7 +242,7 @@ def view_product_frame(parent, switch_func, API_URL, token, product_data: Dict[A hover_color="#3b3b3b", width=40, height=40, - command=lambda: switch_func("user_details") + command=lambda: switch_func("user_details"), ) user_icon.pack(pady=10) @@ -251,21 +256,21 @@ def view_product_frame(parent, switch_func, API_URL, token, product_data: Dict[A hover_color="#3b3b3b", width=40, height=40, - command=lambda: switch_func("user_orders") + command=lambda: switch_func("user_orders"), ) cart_icon.pack(pady=10) # ------------- MAIN CONTENT ------------- main_content = ctk.CTkFrame(frame, fg_color="transparent") main_content.pack(side="left", fill="both", expand=True) - + def refresh_data(new_product_data: Dict[Any, Any]): """Update the frame with new product data""" nonlocal product_data if new_product_data: product_data = new_product_data update_product_display() - + def update_product_display(): """Update all product information displays""" if not product_data: @@ -283,7 +288,7 @@ def view_product_frame(parent, switch_func, API_URL, token, product_data: Dict[A try: # Update product name product_name.configure(text=product_data.get("name", "Product Name")) - + # Update main image if product_images := product_data.get("images", []): if img_url := product_images[0].get("image_url"): @@ -291,11 +296,13 @@ def view_product_frame(parent, switch_func, API_URL, token, product_data: Dict[A try: resp = requests.get(fixed_url) if resp.status_code == 200: - pil_img = Image.open(io.BytesIO(resp.content)).resize((400, 400)) + pil_img = Image.open(io.BytesIO(resp.content)).resize( + (400, 400) + ) tk_img = ImageTk.PhotoImage(pil_img) main_image_label.configure(image=tk_img, text="") main_image_label.image = tk_img - + # Update thumbnails for idx, img_data in enumerate(product_images[:4]): if img_url := img_data.get("image_url"): @@ -303,13 +310,21 @@ def view_product_frame(parent, switch_func, API_URL, token, product_data: Dict[A try: resp = requests.get(fixed_url) if resp.status_code == 200: - pil_img = Image.open(io.BytesIO(resp.content)).resize((80, 80)) + pil_img = Image.open( + io.BytesIO(resp.content) + ).resize((80, 80)) tk_img = ImageTk.PhotoImage(pil_img) - thumb_labels[idx].configure(image=tk_img, text="") + thumb_labels[idx].configure( + image=tk_img, text="" + ) thumb_labels[idx].image = tk_img except Exception as e: - print(f"[DEBUG] Failed to load thumbnail {idx}: {e}") - thumb_labels[idx].configure(text=f"Thumb {idx+1}") + print( + f"[DEBUG] Failed to load thumbnail {idx}: {e}" + ) + thumb_labels[idx].configure( + text=f"Thumb {idx + 1}" + ) except Exception as e: print(f"[DEBUG] Failed to load product image: {e}") main_image_label.configure(text="Image not available") @@ -317,122 +332,114 @@ def view_product_frame(parent, switch_func, API_URL, token, product_data: Dict[A main_image_label.configure(text="No image available") for thumb_label in thumb_labels: thumb_label.configure(text="No image") - + # Update price current_price = product_data.get("price", 0) price_label.configure(text=f"₫{current_price:,.0f}") - + # Update description description_text.configure(state="normal") description_text.delete("1.0", "end") - description_text.insert("1.0", product_data.get("description", "No description available.")) + description_text.insert( + "1.0", product_data.get("description", "No description available.") + ) description_text.configure(state="disabled") # Enable add to cart button add_to_cart_btn.configure(state="normal") - + except Exception as e: messagebox.showerror("Error", f"Failed to update product display: {str(e)}") - + # Main container with two columns main_container = ctk.CTkFrame(main_content, fg_color="transparent") main_container.pack(fill="both", expand=True, padx=20, pady=20) - + # Left column for images left_column = ctk.CTkFrame(main_container, fg_color="transparent") left_column.pack(side="left", fill="both", expand=True) - + # Main image display main_image_frame = ctk.CTkFrame(left_column, fg_color=CARD_BG, corner_radius=10) main_image_frame.pack(fill="both", expand=True, padx=10, pady=10) - - main_image_label = ctk.CTkLabel(main_image_frame, text="Select a product to view details") + + main_image_label = ctk.CTkLabel( + main_image_frame, text="Select a product to view details" + ) main_image_label.pack(padx=20, pady=20) - + # Thumbnail container thumbnail_container = ctk.CTkFrame(left_column, fg_color="transparent") thumbnail_container.pack(fill="x", pady=10) - + # Create thumbnail frames and labels thumb_labels = [] for i in range(4): - thumb_frame = ctk.CTkFrame(thumbnail_container, fg_color=CARD_BG, corner_radius=5) + thumb_frame = ctk.CTkFrame( + thumbnail_container, fg_color=CARD_BG, corner_radius=5 + ) thumb_frame.pack(side="left", padx=5) - thumb_label = ctk.CTkLabel(thumb_frame, text=f"Thumb {i+1}") + thumb_label = ctk.CTkLabel(thumb_frame, text=f"Thumb {i + 1}") thumb_label.pack(padx=5, pady=5) thumb_labels.append(thumb_label) # Right column for product details right_column = ctk.CTkFrame(main_container, fg_color="transparent") right_column.pack(side="right", fill="both", expand=True, padx=20) - + # Product name product_name = ctk.CTkLabel( right_column, text="No product selected", font=("Helvetica", 24, "bold"), justify="left", - wraplength=400 + wraplength=400, ) product_name.pack(anchor="w", pady=(0, 10)) - + # Price price_frame = ctk.CTkFrame(right_column, fg_color="transparent") price_frame.pack(fill="x", pady=10) - + price_label = ctk.CTkLabel( - price_frame, - text="₫0", - font=("Helvetica", 24, "bold"), - text_color="#ff4242" + price_frame, text="₫0", font=("Helvetica", 24, "bold"), text_color="#ff4242" ) price_label.pack(side="left") - + # Quantity selector quantity_frame = ctk.CTkFrame(right_column, fg_color="transparent") quantity_frame.pack(fill="x", pady=20) - + quantity_label = ctk.CTkLabel( - quantity_frame, - text="Quantity:", - font=("Helvetica", 16, "bold") + quantity_frame, text="Quantity:", font=("Helvetica", 16, "bold") ) quantity_label.pack(anchor="w", pady=(0, 10)) - + quantity_selector = ctk.CTkFrame(quantity_frame, fg_color="#2b2b2b") quantity_selector.pack(side="left") - + quantity_var = ctk.IntVar(value=1) - + def update_quantity(delta): new_val = quantity_var.get() + delta if 1 <= new_val <= 99: quantity_var.set(new_val) - + minus_btn = ctk.CTkButton( - quantity_selector, - text="-", - width=30, - command=lambda: update_quantity(-1) + quantity_selector, text="-", width=30, command=lambda: update_quantity(-1) ) minus_btn.pack(side="left", padx=5, pady=5) - + quantity_entry = ctk.CTkEntry( - quantity_selector, - textvariable=quantity_var, - width=50, - justify="center" + quantity_selector, textvariable=quantity_var, width=50, justify="center" ) quantity_entry.pack(side="left", padx=5, pady=5) - + plus_btn = ctk.CTkButton( - quantity_selector, - text="+", - width=30, - command=lambda: update_quantity(1) + quantity_selector, text="+", width=30, command=lambda: update_quantity(1) ) plus_btn.pack(side="left", padx=5, pady=5) - + def add_to_cart(): if not product_data: messagebox.showwarning("Warning", "No product selected") @@ -442,12 +449,14 @@ def view_product_frame(parent, switch_func, API_URL, token, product_data: Dict[A # Validate quantity quantity = quantity_var.get() if not (1 <= quantity <= 99): - messagebox.showwarning("Warning", "Please select a valid quantity (1-99).") + 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") @@ -459,7 +468,7 @@ def view_product_frame(parent, switch_func, API_URL, token, product_data: Dict[A "shop_id": product_data.get("shop_id"), "product_id": product_data.get("id"), "quantity": quantity, - "price": product_data.get("price", 0) + "price": product_data.get("price", 0), } # Debug print @@ -469,7 +478,7 @@ def view_product_frame(parent, switch_func, API_URL, token, product_data: Dict[A # Make API call to add to cart response = requests.post(f"{API_URL}/cart/add", headers=headers, json=data) - + if response.status_code == 200: messagebox.showinfo("Success", "Product added to cart successfully!") # Optionally switch to cart view @@ -480,7 +489,7 @@ def view_product_frame(parent, switch_func, API_URL, token, product_data: Dict[A # 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: @@ -497,33 +506,28 @@ def view_product_frame(parent, switch_func, API_URL, token, product_data: Dict[A fg_color="#ff4242", hover_color="#ff6b6b", command=add_to_cart, - state="disabled" # Initially disabled + state="disabled", # Initially disabled ) add_to_cart_btn.pack(fill="x", pady=20) - + # Product description description_label = ctk.CTkLabel( - right_column, - text="Product Description", - font=("Helvetica", 16, "bold") + right_column, text="Product Description", font=("Helvetica", 16, "bold") ) description_label.pack(anchor="w", pady=(20, 10)) - + description_text = ctk.CTkTextbox( - right_column, - height=100, - wrap="word", - font=("Helvetica", 12) + right_column, height=100, wrap="word", font=("Helvetica", 12) ) description_text.pack(fill="x") description_text.insert("1.0", "No description available.") description_text.configure(state="disabled") - + # Set frame attributes frame.refresh_data = refresh_data - + # Initial display update if product data is available if product_data: update_product_display() - - return frame \ No newline at end of file + + return frame diff --git a/app/frontend/components/shop/create_shop.py b/app/frontend/components/shop/create_shop.py index ace896353cabf8867422a405cebd45372c35069a..c6bf6647454e744c53904fe818fa1a810cb31a21 100644 --- a/app/frontend/components/shop/create_shop.py +++ b/app/frontend/components/shop/create_shop.py @@ -8,6 +8,7 @@ SHOPPING = "#00c1ff" DARK_BG = "#1f1f1f" CARD_BG = "#2b2b2b" + def create_shop_frame(parent, switch_func, API_URL, token): """ Create shop page with a modern dark theme and improved layout. @@ -26,10 +27,7 @@ def create_shop_frame(parent, switch_func, API_URL, token): top_bar.pack(fill="x", side="top") top_label = ctk.CTkLabel( - top_bar, - text="Create Shop", - text_color="white", - font=("Helvetica", 20, "bold") + top_bar, text="Create Shop", text_color="white", font=("Helvetica", 20, "bold") ) top_label.pack(side="left", padx=25) @@ -45,7 +43,7 @@ def create_shop_frame(parent, switch_func, API_URL, token): command=go_back, width=80, height=35, - font=("Helvetica", 14) + font=("Helvetica", 14), ) back_button.pack(side="right", padx=20, pady=7) @@ -54,15 +52,14 @@ def create_shop_frame(parent, switch_func, API_URL, token): main_section.pack(fill="both", expand=True) # LEFT SIDEBAR - sidebar_frame = ctk.CTkFrame(main_section, width=250, fg_color=CARD_BG, corner_radius=15) + sidebar_frame = ctk.CTkFrame( + main_section, width=250, fg_color=CARD_BG, corner_radius=15 + ) sidebar_frame.pack(side="left", fill="y", padx=20, pady=20) sidebar_frame.pack_propagate(False) sidebar_title = ctk.CTkLabel( - sidebar_frame, - text="Menu", - font=("Helvetica", 18, "bold"), - text_color=SHOPPING + sidebar_frame, text="Menu", font=("Helvetica", 18, "bold"), text_color=SHOPPING ) sidebar_title.pack(pady=(20, 10)) @@ -76,7 +73,7 @@ def create_shop_frame(parent, switch_func, API_URL, token): command=command, height=40, font=("Helvetica", 14), - state="disabled" if is_active else "normal" + state="disabled" if is_active else "normal", ) nav_dashboard = create_nav_button("Dashboard", go_back) @@ -106,7 +103,7 @@ def create_shop_frame(parent, switch_func, API_URL, token): header_frame, text="Create Your Shop", font=("Helvetica", 24, "bold"), - text_color=SHOPPING + text_color=SHOPPING, ) title_label.pack(anchor="w") @@ -114,7 +111,7 @@ def create_shop_frame(parent, switch_func, API_URL, token): header_frame, text="Fill in your shop details to get started", font=("Helvetica", 14), - text_color="gray" + text_color="gray", ) subtitle_label.pack(anchor="w", pady=(5, 0)) @@ -131,10 +128,7 @@ def create_shop_frame(parent, switch_func, API_URL, token): field_frame.pack(fill="x", pady=(0, 15)) label = ctk.CTkLabel( - field_frame, - text=label_text, - font=("Helvetica", 14), - text_color="white" + field_frame, text=label_text, font=("Helvetica", 14), text_color="white" ) label.pack(anchor="w") @@ -145,13 +139,15 @@ def create_shop_frame(parent, switch_func, API_URL, token): placeholder_text=placeholder_text, fg_color="#3b3b3b", border_color=SHOPPING, - text_color="white" + text_color="white", ) entry.pack(fill="x", pady=(5, 0)) return entry shop_name_entry = create_form_field("Shop Name", "Enter your shop name...") - shop_description_entry = create_form_field("Shop Description", "Enter your shop description...") + shop_description_entry = create_form_field( + "Shop Description", "Enter your shop description..." + ) shop_address_entry = create_form_field("Shop Address", "Enter your shop address...") # RIGHT PICTURE SECTION @@ -159,10 +155,7 @@ def create_shop_frame(parent, switch_func, API_URL, token): pic_frame.pack(side="left", fill="both", expand=True) pic_label = ctk.CTkLabel( - pic_frame, - text="Shop Logo", - font=("Helvetica", 18, "bold"), - text_color=SHOPPING + pic_frame, text="Shop Logo", font=("Helvetica", 18, "bold"), text_color=SHOPPING ) pic_label.pack(anchor="w", pady=(0, 10)) @@ -170,10 +163,7 @@ def create_shop_frame(parent, switch_func, API_URL, token): photo_frame.pack(fill="x", pady=(0, 20)) photo_label = ctk.CTkLabel( - photo_frame, - text="No image", - text_color="gray", - font=("Helvetica", 14) + photo_frame, text="No image", text_color="gray", font=("Helvetica", 14) ) photo_label.pack(pady=20) @@ -215,7 +205,7 @@ def create_shop_frame(parent, switch_func, API_URL, token): return headers = {"Authorization": f"Bearer {token}"} - + # Create form data form_data = { "name": (None, shop_name), @@ -225,9 +215,9 @@ def create_shop_frame(parent, switch_func, API_URL, token): try: resp = requests.post( - f"{API_URL}/shops/create", + f"{API_URL}/shops/create", headers=headers, - files=form_data # Use files parameter for form data + files=form_data, # Use files parameter for form data ) if resp.status_code == 200: messagebox.showinfo("Success", "Shop created successfully!") diff --git a/app/frontend/components/shop/view_shop.py b/app/frontend/components/shop/view_shop.py index 2c951fa37be0d0151bc0df47aa6b5b4a7773f1fc..9b66f96b2b38e1a651d3d754cf1fc6c93cbaa90a 100644 --- a/app/frontend/components/shop/view_shop.py +++ b/app/frontend/components/shop/view_shop.py @@ -17,7 +17,7 @@ def fix_url(url): # If the URL starts with "app/static/", remove that part prefix = "app/static/" if url.startswith(prefix): - url = url[len(prefix):] + url = url[len(prefix) :] return f"{BACKEND_HOST}/static/{url}" @@ -48,9 +48,7 @@ def view_shop_frame(parent, switch_func, API_URL, token, shop_id): if not shop_id: print("[DEBUG] No shop ID provided") no_shop_label = ctk.CTkLabel( - container_frame, - text="No shop selected", - font=("Helvetica", 16) + container_frame, text="No shop selected", font=("Helvetica", 16) ) no_shop_label.pack(pady=20) return @@ -62,7 +60,7 @@ def view_shop_frame(parent, switch_func, API_URL, token, shop_id): command=lambda: switch_func("dashboard"), fg_color=SHOPPING, text_color="white", - width=150 + width=150, ) back_button.pack(anchor="nw", padx=20, pady=10) @@ -76,7 +74,7 @@ def view_shop_frame(parent, switch_func, API_URL, token, shop_id): print(f"[DEBUG] Fetching shop data for ID: {shop_id}") response = requests.get(f"{API_URL}/shops/get/{shop_id}", headers=headers) print(f"[DEBUG] Response status: {response.status_code}") - + if response.status_code == 200: data = response.json() shop_data = data.get("shop", {}) @@ -85,7 +83,10 @@ def view_shop_frame(parent, switch_func, API_URL, token, shop_id): print(f"[DEBUG] Number of products: {len(products)}") else: print(f"[DEBUG] Error response: {response.text}") - messagebox.showerror("Error", f"Failed to fetch shop data. Status: {response.status_code}") + messagebox.showerror( + "Error", + f"Failed to fetch shop data. Status: {response.status_code}", + ) return except Exception as e: print(f"[DEBUG] Error fetching shop data: {e}") @@ -116,7 +117,9 @@ def view_shop_frame(parent, switch_func, API_URL, token, shop_id): shop_image_label.configure(image=tk_img, text="") shop_image_label.image = tk_img else: - print(f"[DEBUG] Failed to load shop image. Status: {resp.status_code}") + print( + f"[DEBUG] Failed to load shop image. Status: {resp.status_code}" + ) except Exception as e: print(f"[DEBUG] Shop image error: {e}") @@ -128,7 +131,7 @@ def view_shop_frame(parent, switch_func, API_URL, token, shop_id): header_right, text=shop_data.get("name", "Shop Name"), font=("Helvetica", 24, "bold"), - text_color=SHOPPING + text_color=SHOPPING, ) shop_name.pack(anchor="w") @@ -137,7 +140,7 @@ def view_shop_frame(parent, switch_func, API_URL, token, shop_id): text=shop_data.get("description", "No description available."), font=("Helvetica", 14), wraplength=500, - justify="left" + justify="left", ) shop_description.pack(anchor="w", pady=10) @@ -145,7 +148,7 @@ def view_shop_frame(parent, switch_func, API_URL, token, shop_id): header_right, text=f"📍 {shop_data.get('address', 'No address provided')}", font=("Helvetica", 12), - justify="left" + justify="left", ) shop_address.pack(anchor="w") @@ -161,14 +164,12 @@ def view_shop_frame(parent, switch_func, API_URL, token, shop_id): products_header, text="Products", font=("Helvetica", 20, "bold"), - text_color=SHOPPING + text_color=SHOPPING, ) products_title.pack(side="left") products_count = ctk.CTkLabel( - products_header, - text=f"({len(products)} items)", - font=("Helvetica", 14) + products_header, text=f"({len(products)} items)", font=("Helvetica", 14) ) products_count.pack(side="left", padx=10) @@ -178,11 +179,7 @@ def view_shop_frame(parent, switch_func, API_URL, token, shop_id): def create_product_card(parent, product_data): card = ctk.CTkFrame( - parent, - fg_color="#2b2b2b", - corner_radius=10, - width=200, - height=280 + parent, fg_color="#2b2b2b", corner_radius=10, width=200, height=280 ) card.pack_propagate(False) @@ -203,7 +200,9 @@ def view_shop_frame(parent, switch_func, API_URL, token, shop_id): try: resp = requests.get(fixed_url) if resp.status_code == 200: - pil_img = Image.open(io.BytesIO(resp.content)).resize((120, 120)) + pil_img = Image.open(io.BytesIO(resp.content)).resize( + (120, 120) + ) tk_img = ImageTk.PhotoImage(pil_img) image_label.configure(image=tk_img, text="") image_label.image = tk_img @@ -219,7 +218,7 @@ def view_shop_frame(parent, switch_func, API_URL, token, shop_id): text=product_data.get("name", "No Name"), font=("Helvetica", 14, "bold"), wraplength=180, - justify="left" + justify="left", ) name_label.pack(anchor="w") @@ -227,7 +226,7 @@ def view_shop_frame(parent, switch_func, API_URL, token, shop_id): details_frame, text=f"₫ {product_data.get('price', 0.0):,.1f}", font=("Helvetica", 14, "bold"), - text_color="#ff4242" + text_color="#ff4242", ) price_label.pack(anchor="w", pady=5) @@ -238,7 +237,7 @@ def view_shop_frame(parent, switch_func, API_URL, token, shop_id): command=lambda p=product_data: switch_func("view_product", p), fg_color=SHOPPING, text_color="white", - height=30 + height=30, ) view_button.pack(side="bottom", pady=5) @@ -249,7 +248,7 @@ def view_shop_frame(parent, switch_func, API_URL, token, shop_id): products_grid, text="No products available in this shop.", font=("Helvetica", 14), - text_color="gray" + text_color="gray", ) no_products_label.pack(pady=20) else: @@ -260,7 +259,7 @@ def view_shop_frame(parent, switch_func, API_URL, token, shop_id): for product in products: product_card = create_product_card(products_grid, product) product_card.grid(row=row, column=col, padx=10, pady=10, sticky="nsew") - + col += 1 if col >= max_cols: col = 0 @@ -276,4 +275,4 @@ def view_shop_frame(parent, switch_func, API_URL, token, shop_id): # Initial load with empty shop_id refresh_data() - return main_frame \ No newline at end of file + return main_frame diff --git a/app/frontend/components/user_details.py b/app/frontend/components/user_details.py index 395b1a43b31aa4b35f0db0f3ac47f5c96f1d3bc4..645916f069beb16942e19cb3fe7e81fb2dbe9d19 100644 --- a/app/frontend/components/user_details.py +++ b/app/frontend/components/user_details.py @@ -21,10 +21,7 @@ def user_details_frame(parent, switch_func, API_URL, token): top_bar.pack(fill="x", side="top") top_label = ctk.CTkLabel( - top_bar, - text="My Profile", - text_color="white", - font=("Helvetica", 20, "bold") + top_bar, text="My Profile", text_color="white", font=("Helvetica", 20, "bold") ) top_label.pack(side="left", padx=25) @@ -40,7 +37,7 @@ def user_details_frame(parent, switch_func, API_URL, token): command=go_back, width=80, height=35, - font=("Helvetica", 14) + font=("Helvetica", 14), ) back_button.pack(side="right", padx=20, pady=7) @@ -49,15 +46,14 @@ def user_details_frame(parent, switch_func, API_URL, token): main_section.pack(fill="both", expand=True) # LEFT SIDEBAR - sidebar_frame = ctk.CTkFrame(main_section, width=250, fg_color=CARD_BG, corner_radius=15) + sidebar_frame = ctk.CTkFrame( + main_section, width=250, fg_color=CARD_BG, corner_radius=15 + ) sidebar_frame.pack(side="left", fill="y", padx=20, pady=20) sidebar_frame.pack_propagate(False) sidebar_title = ctk.CTkLabel( - sidebar_frame, - text="Menu", - font=("Helvetica", 18, "bold"), - text_color=SHOPPING + sidebar_frame, text="Menu", font=("Helvetica", 18, "bold"), text_color=SHOPPING ) sidebar_title.pack(pady=(20, 10)) @@ -71,7 +67,7 @@ def user_details_frame(parent, switch_func, API_URL, token): command=command, height=40, font=("Helvetica", 14), - state="disabled" if is_active else "normal" + state="disabled" if is_active else "normal", ) nav_dashboard = create_nav_button("Dashboard", go_back) @@ -89,7 +85,9 @@ def user_details_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) - become_owner = create_nav_button("Become Shop Owner", lambda: switch_func("create_shop")) + become_owner = create_nav_button( + "Become Shop Owner", lambda: switch_func("create_shop") + ) become_owner.pack(fill="x", padx=15, pady=5) # RIGHT CONTENT (User Details Form) @@ -104,7 +102,7 @@ def user_details_frame(parent, switch_func, API_URL, token): header_frame, text="Profile Information", font=("Helvetica", 24, "bold"), - text_color=SHOPPING + text_color=SHOPPING, ) title_label.pack(anchor="w") @@ -112,7 +110,7 @@ def user_details_frame(parent, switch_func, API_URL, token): header_frame, text="Manage your profile information to keep your account secure", font=("Helvetica", 14), - text_color="gray" + text_color="gray", ) subtitle_label.pack(anchor="w", pady=(5, 0)) @@ -129,10 +127,7 @@ def user_details_frame(parent, switch_func, API_URL, token): field_frame.pack(fill="x", pady=(0, 15)) label = ctk.CTkLabel( - field_frame, - text=label_text, - font=("Helvetica", 14), - text_color="white" + field_frame, text=label_text, font=("Helvetica", 14), text_color="white" ) label.pack(anchor="w") @@ -143,7 +138,7 @@ def user_details_frame(parent, switch_func, API_URL, token): placeholder_text=placeholder_text, fg_color="#3b3b3b", border_color=SHOPPING, - text_color="white" + text_color="white", ) entry.pack(fill="x", pady=(5, 0)) return entry @@ -158,10 +153,7 @@ def user_details_frame(parent, switch_func, API_URL, token): gender_frame.pack(fill="x", pady=(0, 15)) ctk.CTkLabel( - gender_frame, - text="Gender", - font=("Helvetica", 14), - text_color="white" + gender_frame, text="Gender", font=("Helvetica", 14), text_color="white" ).pack(anchor="w") gender_var = ctk.StringVar(value="Male") @@ -176,7 +168,7 @@ def user_details_frame(parent, switch_func, API_URL, token): value=gender, text_color="white", fg_color=SHOPPING, - hover_color="#0096ff" + hover_color="#0096ff", ).pack(side="left", padx=(0, 20)) birthday_entry = create_form_field("Date of Birth", "dd/mm/yyyy") @@ -221,7 +213,7 @@ def user_details_frame(parent, switch_func, API_URL, token): pic_frame, text="Profile Picture", font=("Helvetica", 18, "bold"), - text_color=SHOPPING + text_color=SHOPPING, ) pic_label.pack(anchor="w", pady=(0, 10)) @@ -229,10 +221,7 @@ def user_details_frame(parent, switch_func, API_URL, token): photo_frame.pack(fill="x", pady=(0, 20)) photo_label = ctk.CTkLabel( - photo_frame, - text="No image", - text_color="gray", - font=("Helvetica", 14) + photo_frame, text="No image", text_color="gray", font=("Helvetica", 14) ) photo_label.pack(pady=20) @@ -285,7 +274,9 @@ def user_details_frame(parent, switch_func, API_URL, token): try: resp_pic = requests.get(pic_url) if resp_pic.status_code == 200: - pil_img = Image.open(io.BytesIO(resp_pic.content)).resize((150, 150)) + pil_img = Image.open(io.BytesIO(resp_pic.content)).resize( + (150, 150) + ) tk_img = ImageTk.PhotoImage(pil_img) photo_label.configure(image=tk_img, text="") photo_label.image = tk_img diff --git a/app/frontend/components/user_orders.py b/app/frontend/components/user_orders.py index 8e694e457ce3907c84f704d8d83e2219437ae01f..3aff27bdebf807559b0b6cdab70010519f835903 100644 --- a/app/frontend/components/user_orders.py +++ b/app/frontend/components/user_orders.py @@ -9,14 +9,16 @@ 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):] + url = url[len(prefix) :] return f"{BACKEND_HOST}/static/{url}" + def user_orders_frame(parent, switch_func, API_URL, token): """ A modern user orders page that displays the products the user has purchased @@ -30,10 +32,7 @@ def user_orders_frame(parent, switch_func, API_URL, token): top_bar.pack(fill="x", side="top") top_label = ctk.CTkLabel( - top_bar, - text="My Orders", - text_color="white", - font=("Helvetica", 20, "bold") + top_bar, text="My Orders", text_color="white", font=("Helvetica", 20, "bold") ) top_label.pack(side="left", padx=25) @@ -49,7 +48,7 @@ def user_orders_frame(parent, switch_func, API_URL, token): command=go_back, width=80, height=35, - font=("Helvetica", 14) + font=("Helvetica", 14), ) back_button.pack(side="right", padx=20, pady=7) @@ -58,15 +57,14 @@ def user_orders_frame(parent, switch_func, API_URL, token): main_section.pack(fill="both", expand=True) # LEFT SIDEBAR - sidebar_frame = ctk.CTkFrame(main_section, width=250, fg_color=CARD_BG, corner_radius=15) + sidebar_frame = ctk.CTkFrame( + main_section, width=250, fg_color=CARD_BG, corner_radius=15 + ) sidebar_frame.pack(side="left", fill="y", padx=20, pady=20) sidebar_frame.pack_propagate(False) sidebar_title = ctk.CTkLabel( - sidebar_frame, - text="Menu", - font=("Helvetica", 18, "bold"), - text_color=SHOPPING + sidebar_frame, text="Menu", font=("Helvetica", 18, "bold"), text_color=SHOPPING ) sidebar_title.pack(pady=(20, 10)) @@ -80,7 +78,7 @@ def user_orders_frame(parent, switch_func, API_URL, token): command=command, height=40, font=("Helvetica", 14), - state="disabled" if is_active else "normal" + state="disabled" if is_active else "normal", ) nav_dashboard = create_nav_button("Dashboard", go_back) @@ -101,7 +99,9 @@ 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) - become_owner = create_nav_button("Become Shop Owner", lambda: switch_func("create_shop")) + become_owner = create_nav_button( + "Become Shop Owner", lambda: switch_func("create_shop") + ) become_owner.pack(fill="x", padx=15, pady=5) # RIGHT CONTENT @@ -117,7 +117,7 @@ def user_orders_frame(parent, switch_func, API_URL, token): orders_frame, text="Your Order History", font=("Helvetica", 18, "bold"), - text_color=SHOPPING + text_color=SHOPPING, ) header_label.pack(anchor="w", pady=(0, 20)) @@ -129,15 +129,15 @@ def user_orders_frame(parent, switch_func, API_URL, token): 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) + font=("Helvetica", 14), ) empty_label.pack(pady=20) return @@ -155,24 +155,22 @@ def user_orders_frame(parent, switch_func, API_URL, token): 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( - header_frame, - text=header_text, - font=("Helvetica", 14, "bold") + 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 = { @@ -181,84 +179,84 @@ def user_orders_frame(parent, switch_func, API_URL, token): "SHIPPED": "#9370DB", "DELIVERED": "#32CD32", "COMPLETED": "#32CD32", - "CANCELLED": "#DC143C" + "CANCELLED": "#DC143C", } status_color = status_colors.get(status, "#808080") - + ctk.CTkLabel( header_frame, text=status, font=("Helvetica", 14, "bold"), - text_color=status_color + 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( address_frame, text="Delivery Address:", font=("Helvetica", 12, "bold"), - text_color="#AAAAAA" + 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="white" + 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" + 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), - text_color="white" + 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" + 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 + 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 95740a14b4d72bbc0f39b6849948a5bd7e4f9603..2b780d940981620db8abc1b864c9bc370a155542 100644 --- a/app/frontend/components/user_payments.py +++ b/app/frontend/components/user_payments.py @@ -105,7 +105,7 @@ def user_payments_frame(parent, switch_func, API_URL, token): content_frame, fg_color="transparent", scrollbar_button_color=SHOPPING, - scrollbar_button_hover_color="#0096ff" + scrollbar_button_hover_color="#0096ff", ) content_scrollable.pack(fill="both", expand=True) @@ -160,7 +160,9 @@ def user_payments_frame(parent, switch_func, API_URL, token): saved_payments_label.pack(pady=(0, 20)) # Container for card and buttons with fixed width for proper centering - saved_card_section = ctk.CTkFrame(saved_payments_frame, fg_color="transparent", width=600, height=220) + saved_card_section = ctk.CTkFrame( + saved_payments_frame, fg_color="transparent", width=600, height=220 + ) saved_card_section.pack(expand=True) saved_card_section.pack_propagate(False) saved_card_section.grid_columnconfigure(0, weight=1) @@ -180,11 +182,11 @@ def user_payments_frame(parent, switch_func, API_URL, token): def create_payment_card(payment): """Create or update the saved payment card display""" nonlocal saved_card_frame - + # Clear buttons container first for widget in buttons_container.winfo_children(): widget.destroy() - + # Remove existing card if any if saved_card_frame is not None: saved_card_frame.destroy() @@ -196,16 +198,13 @@ def user_payments_frame(parent, switch_func, API_URL, token): # Create new card frame saved_card_frame = ctk.CTkFrame( - saved_card_container, - corner_radius=15, - height=200, - width=350 + saved_card_container, corner_radius=15, height=200, width=350 ) saved_card_frame.pack(anchor="e") saved_card_frame.pack_propagate(False) # Set card color based on payment method - card_type = payment['payment_method'].upper() + card_type = payment["payment_method"].upper() if card_type == "VISA": saved_card_frame.configure(fg_color="#0066cc") elif card_type == "MASTERCARD": @@ -215,11 +214,7 @@ def user_payments_frame(parent, switch_func, API_URL, token): # Chip image chip_frame = ctk.CTkFrame( - saved_card_frame, - width=50, - height=40, - fg_color="#FFD700", - corner_radius=5 + saved_card_frame, width=50, height=40, fg_color="#FFD700", corner_radius=5 ) chip_frame.place(x=30, y=50) @@ -228,14 +223,16 @@ def user_payments_frame(parent, switch_func, API_URL, token): saved_card_frame, text=card_type, font=("Helvetica", 20, "bold"), - text_color="white" + text_color="white", ) card_type_label.place(x=270, y=20) # Format card number with spaces - raw_number = payment['card_number'] + raw_number = payment["card_number"] # Mask first 12 digits and show last 4 - masked_number = '**** **** **** ' + raw_number[-4:] if len(raw_number) >= 4 else raw_number + masked_number = ( + "**** **** **** " + raw_number[-4:] if len(raw_number) >= 4 else raw_number + ) formatted_number = masked_number # Card number @@ -243,7 +240,7 @@ def user_payments_frame(parent, switch_func, API_URL, token): saved_card_frame, text=formatted_number, font=("Courier", 24, "bold"), - text_color="white" + text_color="white", ) card_number.place(x=30, y=100) @@ -252,16 +249,16 @@ def user_payments_frame(parent, switch_func, API_URL, token): saved_card_frame, text="VALID\nTHRU", font=("Helvetica", 8), - text_color="white" + text_color="white", ) expiry_label.place(x=30, y=140) # Expiry date expiry_date = ctk.CTkLabel( saved_card_frame, - text=payment['expiry_date'], + text=payment["expiry_date"], font=("Helvetica", 14, "bold"), - text_color="white" + text_color="white", ) expiry_date.place(x=30, y=165) @@ -270,7 +267,7 @@ def user_payments_frame(parent, switch_func, API_URL, token): saved_card_frame, text="CARDHOLDER NAME", font=("Helvetica", 14, "bold"), - text_color="white" + text_color="white", ) cardholder_name.place(x=150, y=165) @@ -286,7 +283,7 @@ def user_payments_frame(parent, switch_func, API_URL, token): hover_color="#0095D9", font=("Helvetica", 14, "bold"), border_width=2, - border_color="#80D9FF" + border_color="#80D9FF", ) edit_button.pack(pady=(0, 15)) @@ -302,7 +299,7 @@ def user_payments_frame(parent, switch_func, API_URL, token): hover_color="#D93939", font=("Helvetica", 14, "bold"), border_width=2, - border_color="#FFA4A4" + border_color="#FFA4A4", ) delete_button.pack() @@ -331,8 +328,8 @@ def user_payments_frame(parent, switch_func, API_URL, token): year_entry.delete(0, "end") cvv_entry.delete(0, "end") card_type_var.set("Visa") - if hasattr(frame, 'current_payment_id'): - delattr(frame, 'current_payment_id') + if hasattr(frame, "current_payment_id"): + delattr(frame, "current_payment_id") update_card_preview() # Enable form inputs when clearing enable_form_inputs() @@ -340,38 +337,38 @@ def user_payments_frame(parent, switch_func, API_URL, token): def edit_payment(payment): """Populate form with payment data for editing""" nonlocal saved_card_frame - + try: # Enable form inputs for editing enable_form_inputs() - + # Store payment ID for update - frame.current_payment_id = payment['id'] - + frame.current_payment_id = payment["id"] + # Populate form with existing payment data card_entry.delete(0, "end") - card_entry.insert(0, payment['card_number']) - + card_entry.insert(0, payment["card_number"]) + # Set card type (capitalize first letter) - card_type = payment['payment_method'].capitalize() + card_type = payment["payment_method"].capitalize() card_type_var.set(card_type) - + # Set expiry date - month, year = payment['expiry_date'].split('/') + month, year = payment["expiry_date"].split("/") month_entry.delete(0, "end") month_entry.insert(0, month) year_entry.delete(0, "end") year_entry.insert(0, year) - + # Update both previews update_card_preview() - + # Create a temporary payment object for the saved card preview temp_payment = { - 'id': payment['id'], - 'payment_method': card_type, - 'card_number': payment['card_number'], - 'expiry_date': payment['expiry_date'] + "id": payment["id"], + "payment_method": card_type, + "card_number": payment["card_number"], + "expiry_date": payment["expiry_date"], } create_payment_card(temp_payment) except Exception as e: @@ -380,64 +377,71 @@ def user_payments_frame(parent, switch_func, API_URL, token): 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?"): + + if messagebox.askyesno( + "Confirm Delete", "Are you sure you want to delete this payment method?" + ): try: headers = {"Authorization": f"Bearer {frame.token}"} response = requests.delete( - f"{API_URL}/payment/{payment['id']}", - headers=headers + f"{API_URL}/payment/{payment['id']}", headers=headers ) - + if response.status_code == 200: # Clear UI elements if saved_card_frame is not None: saved_card_frame.destroy() saved_card_frame = None - + # Clear buttons container for widget in buttons_container.winfo_children(): widget.destroy() - + # Clear form and reset state clear_form() - + # Remove the current_payment_id if it exists - if hasattr(frame, 'current_payment_id'): - delattr(frame, 'current_payment_id') - - messagebox.showinfo("Success", "Payment method deleted successfully") - + if hasattr(frame, "current_payment_id"): + delattr(frame, "current_payment_id") + + messagebox.showinfo( + "Success", "Payment method deleted successfully" + ) + # Enable form inputs after successful delete enable_form_inputs() - + # Refresh the UI refresh_payments() else: - error_msg = response.json().get("detail", "Failed to delete payment method") + error_msg = response.json().get( + "detail", "Failed to delete payment method" + ) messagebox.showerror("Error", error_msg) except Exception as e: - messagebox.showerror("Error", f"Failed to delete payment method: {str(e)}") + messagebox.showerror( + "Error", f"Failed to delete payment method: {str(e)}" + ) def refresh_payments(): """Fetch and display the current payment method""" nonlocal saved_card_frame - + try: headers = {"Authorization": f"Bearer {frame.token}"} response = requests.get(f"{API_URL}/payment/user", headers=headers) - + if response.status_code == 200: payments = response.json() # Clear existing card if any if saved_card_frame is not None: saved_card_frame.destroy() saved_card_frame = None - + # Clear buttons container for widget in buttons_container.winfo_children(): widget.destroy() - + if payments and len(payments) > 0: # Display the first/current payment method create_payment_card(payments[0]) @@ -449,7 +453,10 @@ def user_payments_frame(parent, switch_func, API_URL, token): 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.") + 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() @@ -457,7 +464,9 @@ def user_payments_frame(parent, switch_func, API_URL, token): else: # Handle other errors error_msg = response.json().get("detail", "Unknown error") - messagebox.showerror("Error", f"Failed to fetch payment methods: {error_msg}") + messagebox.showerror( + "Error", f"Failed to fetch payment methods: {error_msg}" + ) except Exception as e: # Enable the form anyway so user can add payment clear_form() @@ -552,10 +561,7 @@ def user_payments_frame(parent, switch_func, API_URL, token): preview_frame.pack(fill="x", pady=(20, 10)) preview_label = ctk.CTkLabel( - preview_frame, - text="Card Preview", - font=("Helvetica", 14), - text_color="white" + preview_frame, text="Card Preview", font=("Helvetica", 14), text_color="white" ) preview_label.pack(anchor="center") @@ -564,31 +570,20 @@ def user_payments_frame(parent, switch_func, API_URL, token): preview_container.pack(fill="x", pady=(0, 20)) card_preview = ctk.CTkFrame( - preview_container, - fg_color="#0066cc", - corner_radius=15, - height=200, - width=350 + preview_container, fg_color="#0066cc", corner_radius=15, height=200, width=350 ) card_preview.pack(anchor="center") card_preview.pack_propagate(False) # Chip image (represented as a small rectangle) chip_frame = ctk.CTkFrame( - card_preview, - width=50, - height=40, - fg_color="#FFD700", - corner_radius=5 + card_preview, width=50, height=40, fg_color="#FFD700", corner_radius=5 ) chip_frame.place(x=30, y=50) # Card type label (top right) card_type_preview = ctk.CTkLabel( - card_preview, - text="VISA", - font=("Helvetica", 20, "bold"), - text_color="white" + card_preview, text="VISA", font=("Helvetica", 20, "bold"), text_color="white" ) card_type_preview.place(x=270, y=20) @@ -597,24 +592,18 @@ def user_payments_frame(parent, switch_func, API_URL, token): card_preview, text="**** **** **** ****", font=("Courier", 24, "bold"), - text_color="white" + text_color="white", ) card_number_preview.place(x=30, y=100) # Expiry date preview expiry_label = ctk.CTkLabel( - card_preview, - text="VALID\nTHRU", - font=("Helvetica", 8), - text_color="white" + card_preview, text="VALID\nTHRU", font=("Helvetica", 8), text_color="white" ) expiry_label.place(x=30, y=140) expiry_preview = ctk.CTkLabel( - card_preview, - text="MM/YY", - font=("Helvetica", 14, "bold"), - text_color="white" + card_preview, text="MM/YY", font=("Helvetica", 14, "bold"), text_color="white" ) expiry_preview.place(x=30, y=165) @@ -623,7 +612,7 @@ def user_payments_frame(parent, switch_func, API_URL, token): card_preview, text="CARDHOLDER NAME", font=("Helvetica", 14, "bold"), - text_color="white" + text_color="white", ) cardholder_preview.place(x=150, y=165) @@ -671,7 +660,9 @@ def user_payments_frame(parent, switch_func, API_URL, token): card_num = card_entry.get().strip() if card_num: # Format card number with spaces and mask first 12 digits - masked_num = '**** **** **** ' + card_num[-4:] if len(card_num) >= 4 else card_num + masked_num = ( + "**** **** **** " + card_num[-4:] if len(card_num) >= 4 else card_num + ) card_number_preview.configure(text=masked_num) else: card_number_preview.configure(text="**** **** **** ****") @@ -687,7 +678,7 @@ def user_payments_frame(parent, switch_func, API_URL, token): # Update card type and color card_type = card_type_var.get().upper() card_type_preview.configure(text=card_type) - + # Update card color based on type if card_type == "VISA": card_preview.configure(fg_color="#0066cc") @@ -695,13 +686,15 @@ def user_payments_frame(parent, switch_func, API_URL, token): card_preview.configure(fg_color="#EB001B") elif card_type == "PAYPAL": card_preview.configure(fg_color="#003087") - + # If we're in edit mode, update the saved card preview as well - if hasattr(frame, 'current_payment_id'): + if hasattr(frame, "current_payment_id"): temp_payment = { - 'payment_method': card_type_var.get(), - 'card_number': card_num, - 'expiry_date': f"{month.zfill(2)}/{year.zfill(2)}" if month and year else "MM/YY" + "payment_method": card_type_var.get(), + "card_number": card_num, + "expiry_date": f"{month.zfill(2)}/{year.zfill(2)}" + if month and year + else "MM/YY", } create_payment_card(temp_payment) @@ -720,7 +713,7 @@ def user_payments_frame(parent, switch_func, API_URL, token): def save_payment(): """Save or update payment method""" nonlocal saved_card_frame - + # Validate all fields card_num = card_entry.get().strip() month = month_entry.get().strip() @@ -753,26 +746,24 @@ def user_payments_frame(parent, switch_func, API_URL, token): "payment_method": card_type, "card_number": card_num, "expiry_date": f"{month}/{year}", - "cvv": cvv + "cvv": cvv, } try: headers = {"Authorization": f"Bearer {frame.token}"} - - if hasattr(frame, 'current_payment_id'): + + if hasattr(frame, "current_payment_id"): # Update existing payment response = requests.put( f"{API_URL}/payment/{frame.current_payment_id}", headers=headers, - json=payload + json=payload, ) action = "updated" else: # Add new payment response = requests.post( - f"{API_URL}/payment/add", - headers=headers, - json=payload + f"{API_URL}/payment/add", headers=headers, json=payload ) action = "added" @@ -781,14 +772,16 @@ def user_payments_frame(parent, switch_func, API_URL, token): # Clear form and reset state clear_form() # Remove the current_payment_id if it exists - if hasattr(frame, 'current_payment_id'): - delattr(frame, 'current_payment_id') + if hasattr(frame, "current_payment_id"): + delattr(frame, "current_payment_id") # Refresh to show updated data refresh_payments() # Disable form inputs after successful save disable_form_inputs() else: - error_msg = response.json().get("detail", f"Failed to {action} payment method") + error_msg = response.json().get( + "detail", f"Failed to {action} payment method" + ) messagebox.showerror("Error", error_msg) except Exception as e: messagebox.showerror("Error", f"Failed to save payment method: {str(e)}") diff --git a/app/frontend/main.py b/app/frontend/main.py index 6a4b8328b1463ff7990d6bf5eba07ecc5a318bb8..70d0d796c63914e36fc95de5c11aabdc887e5eb2 100644 --- a/app/frontend/main.py +++ b/app/frontend/main.py @@ -24,7 +24,16 @@ def switch_frame(frame_name, *args): if args and args[0] and isinstance(args[0], str): access_token = args[0] # Update token for all frames that need it - for frame_key in ["dashboard", "user_details", "user_orders", "user_payments", "create_shop", "view_shop", "product", "category"]: + for frame_key in [ + "dashboard", + "user_details", + "user_orders", + "user_payments", + "create_shop", + "view_shop", + "product", + "category", + ]: if frame_key in frames and hasattr(frames[frame_key], "update_token"): frames[frame_key].update_token(access_token) @@ -53,7 +62,9 @@ create_shop = create_shop_frame( root, switch_frame, API_URL, access_token ) # Accepts token # Fix the parameter order for view_shop_frame -view_shop = view_shop_frame(root, switch_frame, API_URL, access_token, None) # Pass shop_id as None initially +view_shop = view_shop_frame( + root, switch_frame, API_URL, access_token, None +) # Pass shop_id as None initially product = create_product_frame(root, switch_frame, API_URL, access_token) view_product = view_product_frame(root, switch_frame, API_URL, access_token, None) category = category_frame(root, switch_frame, API_URL, access_token) diff --git a/app/frontend/utils/api_requests.py b/app/frontend/utils/api_requests.py index 493ad18611594078bdf9a64ddeab2ed548a8075d..5b3a0caa06b1b1c7c3f921d7cd767b7231c1c13a 100644 --- a/app/frontend/utils/api_requests.py +++ b/app/frontend/utils/api_requests.py @@ -165,9 +165,7 @@ def add_payment_method(api_url, token, payment_data): headers = {"Authorization": f"Bearer {token}"} try: response = requests.post( - f"{api_url}/payment/add", - headers=headers, - json=payment_data + f"{api_url}/payment/add", headers=headers, json=payment_data ) return response.status_code, response.json() except requests.exceptions.RequestException as e: @@ -187,22 +185,17 @@ def get_user_profile(api_url, 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" - } + 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 - ) + 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. @@ -216,18 +209,13 @@ def update_user_profile(api_url, token, profile_data): 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" - } + 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 + f"{api_url}/auth/profile", headers=headers, json=profile_data ) return response.status_code, response.json() except requests.exceptions.RequestException as e: