From f2245f5f07e10e2aab12b0ee1291af4c09d2bc7d Mon Sep 17 00:00:00 2001
From: nn2-minh <Nguyen12.Minh@live.uwe.ac.uk>
Date: Tue, 25 Mar 2025 21:55:18 +0700
Subject: [PATCH] Add user_details and orders

---
 app/frontend/components/auth/login.py   |  13 +-
 app/frontend/components/dashboard.py    |  67 ++++++
 app/frontend/components/user_details.py | 297 ++++++++++++++++++++++++
 app/frontend/components/user_orders.py  | 228 ++++++++++++++++++
 app/frontend/main.py                    |   8 +-
 requirements.txt                        | Bin 1045 -> 2206 bytes
 6 files changed, 608 insertions(+), 5 deletions(-)
 create mode 100644 app/frontend/components/user_details.py
 create mode 100644 app/frontend/components/user_orders.py

diff --git a/app/frontend/components/auth/login.py b/app/frontend/components/auth/login.py
index f2330a5..79f360b 100644
--- a/app/frontend/components/auth/login.py
+++ b/app/frontend/components/auth/login.py
@@ -3,6 +3,7 @@ from tkinter import messagebox
 from PIL import Image
 from utils.api_requests import login_api  # Import the login function from login_api.py
 
+
 def login_frame(parent, switch_func, API_URL):
     # Create a container frame to hold both left (empty) and right (login) frames
     container = ctk.CTkFrame(parent)
@@ -16,10 +17,12 @@ def login_frame(parent, switch_func, API_URL):
 
     # Load and display the image
     image_path = "app/static/front_end_img/login.jpg"  # Change this to your image path
-    img = ctk.CTkImage(light_image=Image.open(image_path), size=(1000, 1000))  # Resize as needed
+    img = ctk.CTkImage(
+        light_image=Image.open(image_path), size=(1000, 1000)
+    )  # Resize as needed
     image_label = ctk.CTkLabel(left_frame, image=img, text="")  # No text, only image
     image_label.place(relwidth=1, relheight=1)
-    
+
     # Right login frame
     right_frame = ctk.CTkFrame(container)
     right_frame.grid(row=0, column=1, sticky="nsew")
@@ -38,14 +41,16 @@ def login_frame(parent, switch_func, API_URL):
         if status_code == 200:
             access_token = response_data.get("access_token")
             messagebox.showinfo("Login Successful", f"Welcome back, {email}!")
-            switch_func("view_shop", access_token)
+            switch_func("dashboard", access_token)
             print(f"Access Token in login: {access_token}")  # Debugging line
         else:
             messagebox.showerror(
                 "Login Failed", response_data.get("detail", "Invalid credentials")
             )
 
-    ctk.CTkLabel(right_frame, text="Login", font=("Helvetica", 18, "bold")).pack(pady=10)
+    ctk.CTkLabel(right_frame, text="Login", font=("Helvetica", 18, "bold")).pack(
+        pady=10
+    )
 
     ctk.CTkLabel(right_frame, text="Email:").pack(pady=5)
     entry_email = ctk.CTkEntry(right_frame)
diff --git a/app/frontend/components/dashboard.py b/app/frontend/components/dashboard.py
index 934e685..75622c9 100644
--- a/app/frontend/components/dashboard.py
+++ b/app/frontend/components/dashboard.py
@@ -57,6 +57,73 @@ def dashboard_frame(parent, switch_func, API_URL, token):
     )
     search_button.place(relx=0.71, rely=0.25, relwidth=0.08, relheight=0.5)
 
+    # ------------- USER & CART ICONS (Top-Right) -------------
+    def open_user_details():
+        # Switch to user_details.py screen or run it
+        switch_func("user_details")
+        # Alternatively, use os.system("python user_details.py")
+
+    def open_cart_details():
+        # Switch to cart_shopping.py screen or run it
+        switch_func("cart_shopping")
+        # Alternatively, use os.system("python cart_shopping.py")
+
+    # Try loading icon images; update paths as needed.
+    try:
+        user_image = Image.open("path/to/user_icon.png").resize((30, 30))
+        user_icon = ImageTk.PhotoImage(user_image)
+    except Exception as e:
+        print(f"User icon load error: {e}")
+        user_icon = None
+
+    try:
+        cart_image = Image.open("path/to/cart_icon.png").resize((30, 30))
+        cart_icon = ImageTk.PhotoImage(cart_image)
+    except Exception as e:
+        print(f"Cart icon load error: {e}")
+        cart_icon = None
+
+    if user_icon:
+        user_button = ctk.CTkButton(
+            header_frame,
+            image=user_icon,
+            text="",
+            fg_color="transparent",
+            command=open_user_details,
+        )
+        switch_func("user_details")
+        user_button.image = user_icon
+        user_button.place(relx=0.82, rely=0.25, relwidth=0.08, relheight=0.5)
+    else:
+        user_button = ctk.CTkButton(
+            header_frame,
+            text="User",
+            fg_color="white",
+            text_color="black",
+            command=open_user_details,
+        )
+        user_button.place(relx=0.82, rely=0.25, relwidth=0.08, relheight=0.5)
+
+    if cart_icon:
+        cart_button = ctk.CTkButton(
+            header_frame,
+            image=cart_icon,
+            text="",
+            fg_color="transparent",
+            command=open_cart_details,
+        )
+        cart_button.image = cart_icon
+        cart_button.place(relx=0.91, rely=0.25, relwidth=0.08, relheight=0.5)
+    else:
+        cart_button = ctk.CTkButton(
+            header_frame,
+            text="Cart",
+            fg_color="white",
+            text_color="black",
+            command=open_cart_details,
+        )
+        cart_button.place(relx=0.91, rely=0.25, relwidth=0.08, relheight=0.5)
+
     # ------------- MIDDLE (Featured/Top Shops) -------------
     middle_frame = ctk.CTkFrame(frame, fg_color="transparent")
     middle_frame.place(relx=0, rely=0.1, relwidth=1, relheight=0.25)
diff --git a/app/frontend/components/user_details.py b/app/frontend/components/user_details.py
new file mode 100644
index 0000000..7a49032
--- /dev/null
+++ b/app/frontend/components/user_details.py
@@ -0,0 +1,297 @@
+import customtkinter as ctk
+import requests
+from PIL import Image, ImageTk
+from tkinter import messagebox, filedialog
+import io
+
+SHOPPING = "#00c1ff"
+
+def user_details_frame(parent, switch_func, API_URL, token):
+    """
+    A two-column user details page that matches the dashboard color scheme:
+      - Top bar with SHOPPING color (#00c1ff)
+      - Left sidebar with a dark background (#2b2b2b)
+      - Right content area (profile form, profile picture)
+    """
+
+    # Main container frame (transparent background, like dashboard)
+    frame = ctk.CTkFrame(parent, fg_color="transparent")
+
+    # ----------------- TOP BAR -----------------
+    top_bar = ctk.CTkFrame(frame, fg_color=SHOPPING, height=40)
+    top_bar.pack(fill="x", side="top")
+
+    top_label = ctk.CTkLabel(
+        top_bar,
+        text="My Profile",
+        text_color="white",
+        font=("Helvetica", 16, "bold"),
+    )
+    top_label.pack(side="left", padx=20)
+
+    def go_back():
+        switch_func("dashboard")
+
+    back_button = ctk.CTkButton(
+        top_bar,
+        text="Back",
+        fg_color="white",
+        text_color="black",
+        command=go_back,
+        width=60,
+        height=30,
+    )
+    back_button.pack(side="right", padx=20, pady=5)
+
+    # ----------------- MAIN SECTION (Sidebar + Content) -----------------
+    main_section = ctk.CTkFrame(frame, fg_color="transparent")
+    main_section.pack(fill="both", expand=True)
+
+    # ----------------- LEFT SIDEBAR -----------------
+
+    sidebar_frame = ctk.CTkFrame(main_section, width=200, fg_color="#2b2b2b")
+    sidebar_frame.pack(side="left", fill="y")
+
+    sidebar_title = ctk.CTkLabel(
+        sidebar_frame, 
+        text="Menu", 
+        font=("Helvetica", 14, "bold"), 
+        text_color="white"
+    )
+    sidebar_title.pack(pady=(10, 5))
+
+    nav_dashboard = ctk.CTkButton(
+        sidebar_frame,
+        text="Dashboard",
+        fg_color="#2b2b2b",
+        text_color="white",
+        hover_color="#3b3b3b",
+        command=go_back
+    )
+    nav_dashboard.pack(fill="x", padx=10, pady=5)
+
+    nav_profile = ctk.CTkButton(
+        sidebar_frame,
+        text="My Profile",
+        fg_color="#3b3b3b",  # Active/selected state
+        text_color="white",
+        hover_color="#3b3b3b",
+        state="disabled"
+    )
+    nav_profile.pack(fill="x", padx=10, pady=5)
+
+    nav_orders = ctk.CTkButton(
+        sidebar_frame,
+        text="My Orders",
+        fg_color="#2b2b2b",
+        text_color="white",
+        hover_color="#3b3b3b",
+        command=lambda: switch_func("user_orders")
+    )
+    nav_orders.pack(fill="x", padx=10, pady=5)
+
+    nav_address = ctk.CTkButton(
+        sidebar_frame,
+        text="Address Book",
+        fg_color="#2b2b2b",
+        text_color="white",
+        hover_color="#3b3b3b",
+        command=lambda: messagebox.showinfo("Info", "Address Book clicked!")
+    )
+    nav_address.pack(fill="x", padx=10, pady=5)
+
+    # ----------------- RIGHT CONTENT (User Details) -----------------
+    content_frame = ctk.CTkFrame(main_section, fg_color="transparent")
+    content_frame.pack(side="left", fill="both", expand=True, padx=20, pady=20)
+
+    # Title / Subtitle
+    title_label = ctk.CTkLabel(
+        content_frame,
+        text="My Profile",
+        font=("Helvetica", 18, "bold"),
+        text_color="white"
+    )
+    title_label.pack(anchor="w", pady=(0, 5))
+
+    subtitle_label = ctk.CTkLabel(
+        content_frame,
+        text="Manage your profile information to keep your account secure",
+        font=("Helvetica", 12),
+        text_color="#cccccc"
+    )
+    subtitle_label.pack(anchor="w", pady=(0, 15))
+
+    # -- Split the right content into two frames: Left form & Right picture --
+    right_main = ctk.CTkFrame(content_frame, fg_color="transparent")
+    right_main.pack(fill="both", expand=True)
+
+    # LEFT FORM
+    form_frame = ctk.CTkFrame(right_main, fg_color="transparent")
+    form_frame.pack(side="left", fill="both", expand=True, padx=(0, 20))
+
+    # Username
+    username_label = ctk.CTkLabel(form_frame, text="Username", font=("Helvetica", 12), text_color="white")
+    username_label.pack(anchor="w")
+    username_entry = ctk.CTkEntry(form_frame, placeholder_text="Enter your username...")
+    username_entry.pack(anchor="w", pady=(0, 10))
+
+    # Name
+    name_label = ctk.CTkLabel(form_frame, text="Name", font=("Helvetica", 12), text_color="white")
+    name_label.pack(anchor="w")
+    name_entry = ctk.CTkEntry(form_frame, placeholder_text="Enter your name...")
+    name_entry.pack(anchor="w", pady=(0, 10))
+
+    # Email
+    email_label = ctk.CTkLabel(form_frame, text="Email", font=("Helvetica", 12), text_color="white")
+    email_label.pack(anchor="w")
+    email_entry = ctk.CTkEntry(form_frame, placeholder_text="Enter your email...")
+    email_entry.pack(anchor="w", pady=(0, 10))
+
+    # Phone
+    phone_label = ctk.CTkLabel(form_frame, text="Phone", font=("Helvetica", 12), text_color="white")
+    phone_label.pack(anchor="w")
+    phone_entry = ctk.CTkEntry(form_frame, placeholder_text="Enter your phone...")
+    phone_entry.pack(anchor="w", pady=(0, 10))
+
+    # Gender
+    gender_label = ctk.CTkLabel(form_frame, text="Gender", font=("Helvetica", 12), text_color="white")
+    gender_label.pack(anchor="w", pady=(10, 0))
+
+    gender_var = ctk.StringVar(value="Male")  # Default
+    gender_frame = ctk.CTkFrame(form_frame, fg_color="transparent")
+    gender_frame.pack(anchor="w", pady=5)
+
+    male_radio = ctk.CTkRadioButton(gender_frame, text="Male", variable=gender_var, value="Male", text_color="white")
+    female_radio = ctk.CTkRadioButton(gender_frame, text="Female", variable=gender_var, value="Female", text_color="white")
+    other_radio = ctk.CTkRadioButton(gender_frame, text="Other", variable=gender_var, value="Other", text_color="white")
+    male_radio.pack(side="left", padx=5)
+    female_radio.pack(side="left", padx=5)
+    other_radio.pack(side="left", padx=5)
+
+    # Birthday
+    birthday_label = ctk.CTkLabel(form_frame, text="Date of Birth", font=("Helvetica", 12), text_color="white")
+    birthday_label.pack(anchor="w", pady=(10, 0))
+    birthday_entry = ctk.CTkEntry(form_frame, placeholder_text="dd/mm/yyyy")
+    birthday_entry.pack(anchor="w", pady=(0, 10))
+
+    # Save Button
+    def save_profile():
+        """
+        Example: Send a request to update user details.
+        Adjust the endpoint and payload for your backend.
+        """
+        headers = {"Authorization": f"Bearer {token}"}
+        payload = {
+            "username": username_entry.get().strip(),
+            "name": name_entry.get().strip(),
+            "email": email_entry.get().strip(),
+            "phone": phone_entry.get().strip(),
+            "gender": gender_var.get(),
+            "birthday": birthday_entry.get().strip()
+        }
+        try:
+            resp = requests.put(f"{API_URL}/user/update", headers=headers, json=payload)
+            if resp.status_code == 200:
+                messagebox.showinfo("Success", "Profile updated successfully!")
+            else:
+                messagebox.showerror("Error", "Unable to update profile.")
+        except Exception as e:
+            messagebox.showerror("Error", f"Request error: {e}")
+
+    save_button = ctk.CTkButton(
+        form_frame,
+        text="Save",
+        fg_color=SHOPPING,  # #00c1ff
+        text_color="white",
+        command=save_profile,
+        width=80
+    )
+    save_button.pack(anchor="w", pady=(20, 10))
+
+    # RIGHT PICTURE SECTION
+    pic_frame = ctk.CTkFrame(right_main, fg_color="transparent")
+    pic_frame.pack(side="left", fill="both", expand=True)
+
+    pic_label = ctk.CTkLabel(pic_frame, text="Profile Picture", font=("Helvetica", 12, "bold"), text_color="white")
+    pic_label.pack(anchor="w", pady=(0, 10))
+
+    photo_label = ctk.CTkLabel(pic_frame, text="No image", text_color="white")
+    photo_label.pack(anchor="w", pady=(0, 10))
+
+    def choose_photo():
+        """
+        Let the user pick a photo from their files.
+        Then optionally upload it or just display it.
+        """
+        file_path = filedialog.askopenfilename(
+            title="Choose a Profile Picture",
+            filetypes=[("Image Files", "*.png *.jpg *.jpeg *.gif")]
+        )
+        if file_path:
+            # Display chosen image
+            try:
+                pil_img = Image.open(file_path).resize((100, 100))
+                tk_img = ImageTk.PhotoImage(pil_img)
+                photo_label.configure(image=tk_img, text="")
+                photo_label.image = tk_img
+
+                # Optionally, upload the file to your server:
+                # headers = {"Authorization": f"Bearer {token}"}
+                # with open(file_path, 'rb') as f:
+                #     requests.post(f'{API_URL}/user/upload_photo', files={'file': f}, headers=headers)
+            except Exception as e:
+                print(f"Image load error: {e}")
+                messagebox.showerror("Error", "Cannot load the image.")
+
+    change_photo_button = ctk.CTkButton(
+        pic_frame,
+        text="Choose Image",
+        fg_color="white",
+        text_color="black",
+        command=choose_photo,
+        width=80
+    )
+    change_photo_button.pack(anchor="w")
+
+    # Fetch existing user info (if needed)
+    def fetch_user_info():
+        """
+        Fetch the user's existing data to populate the form fields.
+        """
+        headers = {"Authorization": f"Bearer {token}"}
+        try:
+            resp = requests.get(f"{API_URL}/user/profile", headers=headers)
+            if resp.status_code == 200:
+                data = resp.json()
+                username_entry.delete(0, "end")
+                username_entry.insert(0, data.get("username", ""))
+                name_entry.delete(0, "end")
+                name_entry.insert(0, data.get("name", ""))
+                email_entry.delete(0, "end")
+                email_entry.insert(0, data.get("email", ""))
+                phone_entry.delete(0, "end")
+                phone_entry.insert(0, data.get("phone", ""))
+                gender_var.set(data.get("gender", "Male"))
+                birthday_entry.delete(0, "end")
+                birthday_entry.insert(0, data.get("birthday", ""))
+
+                # If there's a profile picture URL, load it
+                pic_url = data.get("photo_url")
+                if pic_url:
+                    try:
+                        resp_pic = requests.get(pic_url)
+                        if resp_pic.status_code == 200:
+                            pil_img = Image.open(io.BytesIO(resp_pic.content)).resize((100, 100))
+                            tk_img = ImageTk.PhotoImage(pil_img)
+                            photo_label.configure(image=tk_img, text="")
+                            photo_label.image = tk_img
+                    except Exception as e:
+                        print(f"Profile picture load error: {e}")
+            else:
+                messagebox.showerror("Error", "Unable to retrieve user information.")
+        except Exception as e:
+            messagebox.showerror("Error", f"Request error: {e}")
+
+    fetch_user_info()
+
+    return frame
diff --git a/app/frontend/components/user_orders.py b/app/frontend/components/user_orders.py
new file mode 100644
index 0000000..b71b34f
--- /dev/null
+++ b/app/frontend/components/user_orders.py
@@ -0,0 +1,228 @@
+import customtkinter as ctk
+import requests
+from PIL import Image, ImageTk
+from tkinter import messagebox
+import io
+
+SHOPPING = "#00c1ff"
+
+def user_orders_frame(parent, switch_func, API_URL, token):
+    """
+    A two-column user orders page that displays the products the user has purchased 
+    along with associated shop details. The layout and color scheme match your dashboard.
+    """
+
+    # Main container frame with transparent background
+    frame = ctk.CTkFrame(parent, fg_color="transparent")
+
+    # ----------------- TOP BAR -----------------
+    top_bar = ctk.CTkFrame(frame, fg_color=SHOPPING, height=40)
+    top_bar.pack(fill="x", side="top")
+
+    top_label = ctk.CTkLabel(
+        top_bar,
+        text="My Orders",
+        text_color="white",
+        font=("Helvetica", 16, "bold")
+    )
+    top_label.pack(side="left", padx=20)
+
+    def go_back():
+        switch_func("dashboard")
+
+    back_button = ctk.CTkButton(
+        top_bar,
+        text="Back",
+        fg_color="white",
+        text_color="black",
+        command=go_back,
+        width=60,
+        height=30
+    )
+    back_button.pack(side="right", padx=20, pady=5)
+
+    # ----------------- MAIN SECTION (Sidebar + Content) -----------------
+    main_section = ctk.CTkFrame(frame, fg_color="transparent")
+    main_section.pack(fill="both", expand=True)
+
+    # ----------------- LEFT SIDEBAR -----------------
+    sidebar_frame = ctk.CTkFrame(main_section, width=200, fg_color="#2b2b2b")
+    sidebar_frame.pack(side="left", fill="y")
+
+    sidebar_title = ctk.CTkLabel(
+        sidebar_frame,
+        text="Menu",
+        font=("Helvetica", 14, "bold"),
+        text_color="white"
+    )
+    sidebar_title.pack(pady=(10, 5))
+
+    def open_profile():
+        switch_func("user_details")
+
+    nav_dashboard = ctk.CTkButton(
+        sidebar_frame,
+        text="Dashboard",
+        fg_color="#2b2b2b",
+        text_color="white",
+        hover_color="#3b3b3b",
+        command=go_back
+    )
+    nav_dashboard.pack(fill="x", padx=10, pady=5)
+
+    nav_profile = ctk.CTkButton(
+        sidebar_frame,
+        text="My Profile",
+        fg_color="#2b2b2b",
+        text_color="white",
+        hover_color="#3b3b3b",
+        command=open_profile
+    )
+    nav_profile.pack(fill="x", padx=10, pady=5)
+
+    nav_orders = ctk.CTkButton(
+        sidebar_frame,
+        text="My Orders",
+        fg_color="#3b3b3b",  # Active/selected state
+        text_color="white",
+        hover_color="#3b3b3b",
+        state="disabled"
+    )
+    nav_orders.pack(fill="x", padx=10, pady=5)
+
+    # ----------------- RIGHT CONTENT (Orders List) -----------------
+    content_frame = ctk.CTkFrame(main_section, fg_color="transparent")
+    content_frame.pack(side="left", fill="both", expand=True, padx=20, pady=20)
+
+    main_title_label = ctk.CTkLabel(
+        content_frame,
+        text="Your Orders",
+        font=("Helvetica", 18, "bold"),
+        text_color="white"
+    )
+    main_title_label.pack(anchor="w", pady=(0, 5))
+
+    subtitle_label = ctk.CTkLabel(
+        content_frame,
+        text="Review the products you have purchased.",
+        font=("Helvetica", 12),
+        text_color="#cccccc"
+    )
+    subtitle_label.pack(anchor="w", pady=(0, 15))
+
+    # A frame to hold the list of orders
+    orders_list_frame = ctk.CTkFrame(content_frame, fg_color="transparent")
+    orders_list_frame.pack(fill="both", expand=True)
+
+    # ----------- Functions to fetch and display orders -----------
+    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()  # Expect a list of order dicts
+                display_orders(orders, orders_list_frame)
+            else:
+                messagebox.showerror("Error", "Failed to fetch orders.")
+        except Exception as ex:
+            messagebox.showerror("Error", f"Request error: {ex}")
+
+    def display_orders(orders, container):
+        """
+        Display each order with product and shop details.
+        """
+        # Clear previous content
+        for widget in container.winfo_children():
+            widget.destroy()
+
+        if not orders:
+            ctk.CTkLabel(container, text="No orders found.", text_color="white").pack(pady=10)
+            return
+
+        for order in orders:
+            # Assume each order dict includes a 'product' dict and an 'order_date'
+            product = order.get("product", {})
+            order_date = order.get("order_date", "Unknown Date")
+
+            order_frame = ctk.CTkFrame(container, corner_radius=5, fg_color="#2b2b2b")
+            order_frame.pack(fill="x", padx=5, pady=5)
+
+            # Left: Product image
+            image_label = ctk.CTkLabel(order_frame, text="")
+            image_label.pack(side="left", padx=5, pady=5)
+            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((60, 60))
+                        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}")
+
+            # Right: Order details and shop info
+            info_frame = ctk.CTkFrame(order_frame, fg_color="transparent")
+            info_frame.pack(side="left", fill="both", expand=True, padx=10)
+
+            # Product Name
+            ctk.CTkLabel(
+                info_frame,
+                text=product.get("name", "No Name"),
+                font=("Helvetica", 13, "bold"),
+                text_color="white"
+            ).pack(anchor="w")
+
+            # Price (and order date)
+            price = product.get("price", 0.0)
+            ctk.CTkLabel(
+                info_frame,
+                text=f"Price: {price:.2f}",
+                text_color="#cccccc"
+            ).pack(anchor="w")
+            ctk.CTkLabel(
+                info_frame,
+                text=f"Ordered on: {order_date}",
+                text_color="#cccccc"
+            ).pack(anchor="w")
+
+            # Shop Name
+            shop_id = product.get("shop_id")
+            shop_name_label = ctk.CTkLabel(info_frame, text="Shop: Loading...", text_color="#cccccc")
+            shop_name_label.pack(anchor="w")
+
+            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 (placeholder action)
+            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",
+                fg_color=SHOPPING,
+                text_color="white",
+                command=view_order
+            )
+            view_button.pack(anchor="e", pady=(5, 0))
+
+    # Fetch orders when the frame loads
+    fetch_orders()
+
+    return frame
diff --git a/app/frontend/main.py b/app/frontend/main.py
index 6773394..65b8f0a 100644
--- a/app/frontend/main.py
+++ b/app/frontend/main.py
@@ -5,7 +5,9 @@ from components.shop.create_shop import create_shop_frame
 from components.shop.view_shop import view_shop_frame
 from components.product.create_product import product_frame
 from components.admin.category import category_frame
-from components.dashboard import dashboard_frame  # import the dashboard
+from components.dashboard import dashboard_frame
+from components.user_details import user_details_frame
+from components.user_orders import user_orders_frame
 
 API_URL = "http://127.0.0.1:8000"
 access_token = None
@@ -37,6 +39,8 @@ view_shop = view_shop_frame(root, switch_frame, API_URL, access_token)
 product = product_frame(root, switch_frame, API_URL, access_token)
 category = category_frame(root, switch_frame, API_URL, access_token)
 dashboard = dashboard_frame(root, switch_frame, API_URL, access_token)
+user_details = user_details_frame(root, switch_frame, API_URL, access_token)
+user_orders = user_orders_frame(root, switch_frame, API_URL, access_token)
 
 frames = {
     "login": login,
@@ -46,6 +50,8 @@ frames = {
     "category": category,
     "view_shop": view_shop,
     "dashboard": dashboard,
+    "user_details": user_details,
+    "user_orders": user_orders,
 }
 
 for frame in frames.values():
diff --git a/requirements.txt b/requirements.txt
index 2383f12bc661c32a3b8eac0fcf9146bd60a0f3fe..1c5bb150f1acbbb5b29d84ddfede66a9ed8b7204 100644
GIT binary patch
literal 2206
zcmezWFOeaSA&()Sp@bokp@booA%#Jgp@gB5p@1Qkp_svz!Ir^*L65<lL65<JftP`c
z0i?c?A(J5=EN8-?$6x`MOJYc7C<5y(0m~XQ=rI_9F-T`JLn=cNLkUABLmERSSl)=i
zfWe5tltGWdkU@{Zn86TiLo!1eTm?wn5Ntb4E`uSFp@^ZFA(f$oK^N@uB8FUsM1~xO
zOol434InpzYzDavrVAvS%#aPX735x!xfW2{@)>d%@)?R462azxbc1{h(rpG+0rOKn
zLpoSp0Ye5uCD<$z29P?CdLyXXQifuN5{7(+T!s>cY=%sRJaDKMfmMTiiU^MsuurlX
zQow#mWk_a#gc`^+kQ*W4ox+gEP|Q%kP{~ljkin48kOwviWC|iqQo%0IWXNHNXDEY)
z1;})eJ0LcL)Ppd@%ru5XuqzW83ZO9oveS^k5E>VdP(xLv%a9BXV_5irbeMzvoX(KS
zfE+TJP?bq=mqKiXr~~-~WCqAJps<731q#<xhE#?;h8%EcfK-6u5EM@k6&Va5_kqMg
zHXB029h9yz7)lsQ7z!AY!LgVMwiTogqz>Xjm<mvMfMOewuFV+q7)-#vfT^hf>j&vI
z0;g<<?U@Xqct`}xLEHuLQzk<RLoov=tn(N^{wre0X8@%DkUK#>1Yw9uFK}AOVn}2#
zLNY~<!4zyeC<PZWfYL%fLpehpgDwNeWL<E)gUkZi333y}EMI8KDP<^N2xdrRNMnHb
z8m0=87IPU=7)lw67;?b&fpmjH1twR?P|Q%ski!6qS5T_TV}PVUP)>l@i=5sp!MPY#
z27uILF%-j7BgkGua5(`=L7+G(U`S*D<+ntJbg;kE!EP`D#~Q>1AQi<7#o#mrNhu)L
zfzmW2K0qlm2b?p@!FoZl4=E=gVE{@;$qXP<Af*9vI)~X%35^+0nUf4Q1r#3;mqPM3
z$P`HILDc9nq%u@6lz?+NdO3*Di`{%(XsHA;36vTjsSKnGl$#;x2;IJTlvE2+YXmOs
zAT|UrR5GM9<bv}BD4l^)0!WP^11LTqCW6#>F@!UOp!p9W>&sBd5X=zB-~(0x3S|g}
z#1o=i(Pc;h*8`wjSO!-MNjI2ka~Vn*a=@iIBn}W|7f1)FWC!U1xh|5yk--;gJ1D<F
z<Uz7U45<u-45i?j2HAhc;9A2NoHL3TK;=mW)IFe-2J$;3jlkqUF_q5%%Gsb27vT~^
z29SD4SQay6Fr+eoQWU66PG<nsBe_shLAelC4iq!wfomvG9R;c5K%oUP2NI%?kaT3o
zVMqp>m<z25K=A_dnK1*TUV-GUT!wsb`HskgAT>q|5Wj(H3Q%nZDf2+JFDQmVDohw4
zAq5I0SZxeR;UHN<a5)X}7ep4~r+8@U1;t=7xQ+(Z9UwCyB`wH}5M8AV$oUskFF|qz
zByT}V4N#q##9#~#Balu|7{KJqz_9`<2O#wk$W_MR+znAt4sK0=LI@O&Apb#f11Ky(
bY9aAg&XCHG1TIHFWd*24hp7U&$bbO=_4q;!

literal 1045
zcmYey%gZlGEJ;n#EvYO>Ew;5a&@<OF;7ZJ^%*?m7HPN%sGvG=}E~+djv9&eUGtx8S
zN=_{*$xO?%wKXy@GSxHGGdAQ(P6P1`^$gAR47rjs5{rscOLX({i*gflGOJRHKsrr8
zGC7&a*|xS8dWL!yT*>)4`9+Dji69$I^vt-xcI2lQB^G2<+S;0!=ox_QDlIO_&n?N$
z%qsyIV+wXaN@7uVN@_`JatTN;$bl(&#RZim8Tom(wnkv*r{*SR=ERpJ=47TMmgE<K
zgpKqJxY81fOA-q*LD~!rP4x^7VIsQ8Ibcx(J#((~RER4wlXEhYKspUTLIsr|Cde1*
zMX9NIIjP|AFw`^T$}ltp8)%|uz?D%_QjnZqlxk~hs0T6&#4pLu&jE+4nVtz)K?O*;
zk%gWiS7u6HA}DYS4Y)E(ic=Ev(o>7_ON&7^ft>4=nU|Gl1XiGD%9RW9c7A!DZf1!t
z$Th}#26_fuzKKQIr3Jx>X{jJ}pfJl#DJ{wYSz)MW#FblFT$rPqoS&DMnp~1!qzels
zOFfVU`9)d9pg=R$Gc?dMH03HtOwLYB&&*4=wKX!)GvX>pEG`BIf}x(do(We$W=>9i
zxvj0Cp`M|h0arm~aseo+A;D|JRZy9dm{*dS4067qfu0#$KsU9bq$m-Vz>GkaAyn%Y
zr<Rmt=B0yu4GI*PG4YUqG}1FN*E8Y@s7%jI%_{+`GPKk)<O-<t3J-x=1>*Tu1_%0p
z!W|SR5O3<H<d>x8m4O5e^bEP6g1MzRC7A_@Mc~w9pl4*j6;K)J=<8!^Yo-SZzoOK_
z($r#zO~yug##}|2$r-k`hQ@l9dM03wE+|T~Gr<NJ8tWNx6=$U8<YeZhXC&r=EHl+J
z;VRC{OiKf0AwxZ5JwvYGKp)4P<c!o@a56H`Gq&I=F3iczPe}zwm4Tj-F;{U(Vo^?N
zNl7Xw@=VP147fm9vIxX8G}SW#@iOz$<5Me2QuB&4^Ye;9`O^@TB}*aEXbK8h1Fq7d
zoSe)gV^G)_>ltvBmSraA7v+JBF$Sg4^2CzljI_)gP-X|EQWLK7)THA4<m^<CpAAj*
G47dR3XFVtY

-- 
GitLab