From 51e7cfd8714d244ec4b3030a418911ce8f43c01d Mon Sep 17 00:00:00 2001
From: nn2-minh <Nguyen12.Minh@live.uwe.ac.uk>
Date: Tue, 8 Apr 2025 22:27:53 +0700
Subject: [PATCH] Add payment and view product for frontend, also delete the
 masked code of the payment backend

---
 app/backend/routes/payment.py                 |  20 +-
 app/backend/schemas/payment.py                |  10 +-
 app/backend/utils/security.py                 |   5 -
 .../components/product/view_product.py        | 320 ++++++++++-
 app/frontend/components/user_payments.py      | 506 +++++++++++++++++-
 app/frontend/main.py                          |   3 +
 6 files changed, 827 insertions(+), 37 deletions(-)

diff --git a/app/backend/routes/payment.py b/app/backend/routes/payment.py
index ba9e21e..e012a56 100644
--- a/app/backend/routes/payment.py
+++ b/app/backend/routes/payment.py
@@ -1,5 +1,5 @@
 from fastapi import APIRouter, Depends, HTTPException
-from sqlmodel import Session
+from sqlmodel import Session, select
 from backend.models.models import Payment, User
 from backend.schemas.payment import PaymentCreate, PaymentRead
 from backend.database import get_session
@@ -32,7 +32,19 @@ def add_payment(
     session.commit()
     session.refresh(new_payment)
 
-    return new_payment
+    return PaymentRead.from_orm(new_payment)
+
+
+@router.get("/user", response_model=list[PaymentRead])
+def get_user_payments(
+    session: Session = Depends(get_session),
+    current_user: User = Depends(get_current_user),
+):
+    """Get all payment methods for the current user."""
+    payments = session.exec(
+        select(Payment).where(Payment.user_id == current_user.id)
+    ).all()
+    return [PaymentRead.from_orm(payment) for payment in payments]
 
 
 @router.get("/{payment_id}", response_model=PaymentRead)
@@ -45,7 +57,7 @@ def read_payment(
     payment = session.get(Payment, payment_id)
     if not payment or payment.user_id != current_user.id:
         raise HTTPException(status_code=404, detail="Payment not found")
-    return payment
+    return PaymentRead.from_orm(payment)
 
 
 @router.put("/{payment_id}", response_model=PaymentRead)
@@ -69,7 +81,7 @@ def update_payment(
     session.add(payment)
     session.commit()
     session.refresh(payment)
-    return payment
+    return PaymentRead.from_orm(payment)
 
 
 @router.delete("/{payment_id}", response_model=dict)
diff --git a/app/backend/schemas/payment.py b/app/backend/schemas/payment.py
index 6009043..3d869a4 100644
--- a/app/backend/schemas/payment.py
+++ b/app/backend/schemas/payment.py
@@ -2,7 +2,6 @@ from pydantic import BaseModel, Field, validator
 from datetime import datetime
 from backend.utils.security import (
     encrypt_card_number,
-    mask_card_number,
     decrypt_card_number,
 )
 
@@ -45,18 +44,19 @@ class PaymentRead(BaseModel):
     id: int
     user_id: int
     payment_method: str
-    expiry_date: str  # Keeping expiry date visible
-    created_at: datetime  # Assuming you have a timestamp field
+    card_number: str  # Now storing the actual card number
+    expiry_date: str
+    created_at: datetime
 
     @classmethod
     def from_orm(cls, obj):
-        """Decrypt card number and mask it before returning."""
+        """Decrypt card number before returning."""
         decrypted_card = decrypt_card_number(obj.card_number)
         return cls(
             id=obj.id,
             user_id=obj.user_id,
             payment_method=obj.payment_method,
-            masked_card_number=mask_card_number(decrypted_card),
+            card_number=decrypted_card,  # Return actual card number
             expiry_date=obj.expiry_date,
             created_at=obj.created_at,
         )
diff --git a/app/backend/utils/security.py b/app/backend/utils/security.py
index 9fc9aec..f3b7cbd 100644
--- a/app/backend/utils/security.py
+++ b/app/backend/utils/security.py
@@ -26,8 +26,3 @@ def encrypt_card_number(card_number: str) -> str:
 def decrypt_card_number(encrypted_card: str) -> str:
     """Decrypts the encrypted card number."""
     return fernet.decrypt(encrypted_card.encode()).decode()
-
-
-def mask_card_number(card_number: str) -> str:
-    """Masks the card number, showing only the last 4 digits."""
-    return f"**** **** **** {card_number[-4:]}"
diff --git a/app/frontend/components/product/view_product.py b/app/frontend/components/product/view_product.py
index 5fdca7d..0e84236 100644
--- a/app/frontend/components/product/view_product.py
+++ b/app/frontend/components/product/view_product.py
@@ -3,16 +3,26 @@ import requests
 from PIL import Image, ImageTk
 from tkinter import messagebox
 import io
+from typing import List, Dict, Any
 
-BACKEND_HOST = "http://127.0.0.1:8000"  # Adjust if needed
+SHOPPING = "#00c1ff"
+DARK_BG = "#1f1f1f"
+CARD_BG = "#2b2b2b"
+BACKEND_HOST = "http://127.0.0.1:8000"
 
 
 def fix_url(url):
     """
-    Fix the URL to ensure it points to the correct static file location.
+    If the provided URL does not start with http, assume it's a relative path.
+    Remove any unwanted prefix (e.g., "app/static/") and prepend the public URL.
     """
     if url.startswith("http"):
         return url
+    # If the URL starts with "app/static/", remove that part.
+    prefix = "app/static/"
+    if url.startswith(prefix):
+        url = url[len(prefix):]
+    # Prepend the public URL
     return f"{BACKEND_HOST}/static/{url}"
 
 
@@ -177,4 +187,310 @@ def view_shop_frame(parent, switch_func, API_URL, token, shop_id):
     # Display products
     display_products(products, products_frame)
 
+    return frame
+
+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)
+    
+    # ------------- LEFT SIDEBAR -------------
+    sidebar = ctk.CTkFrame(frame, fg_color=CARD_BG, width=60)
+    sidebar.pack(side="left", fill="y")
+    sidebar.pack_propagate(False)
+
+    # Dashboard Icon
+    dashboard_icon = ctk.CTkButton(
+        sidebar,
+        text="🏠",
+        font=("Helvetica", 20),
+        fg_color="transparent",
+        text_color="white",
+        hover_color="#3b3b3b",
+        width=40,
+        height=40,
+        command=lambda: switch_func("dashboard")
+    )
+    dashboard_icon.pack(pady=(20, 10))
+
+    # User Icon
+    user_icon = ctk.CTkButton(
+        sidebar,
+        text="👤",
+        font=("Helvetica", 20),
+        fg_color="transparent",
+        text_color="white",
+        hover_color="#3b3b3b",
+        width=40,
+        height=40,
+        command=lambda: switch_func("user_details")
+    )
+    user_icon.pack(pady=10)
+
+    # Cart Icon
+    cart_icon = ctk.CTkButton(
+        sidebar,
+        text="🛒",
+        font=("Helvetica", 20),
+        fg_color="transparent",
+        text_color="white",
+        hover_color="#3b3b3b",
+        width=40,
+        height=40,
+        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:
+            # Show placeholder state
+            main_image_label.configure(text="Select a product to view details")
+            product_name.configure(text="No product selected")
+            price_label.configure(text="₫0")
+            description_text.configure(state="normal")
+            description_text.delete("1.0", "end")
+            description_text.insert("1.0", "No description available.")
+            description_text.configure(state="disabled")
+            add_to_cart_btn.configure(state="disabled")
+            return
+
+        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"):
+                    fixed_url = fix_url(img_url)
+                    try:
+                        resp = requests.get(fixed_url)
+                        if resp.status_code == 200:
+                            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"):
+                                    fixed_url = fix_url(img_url)
+                                    try:
+                                        resp = requests.get(fixed_url)
+                                        if resp.status_code == 200:
+                                            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].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}")
+                    except Exception as e:
+                        print(f"[DEBUG] Failed to load product image: {e}")
+                        main_image_label.configure(text="Image not available")
+            else:
+                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.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.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.pack(side="left", padx=5)
+        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
+    )
+    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_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_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)
+    )
+    minus_btn.pack(side="left", padx=5, pady=5)
+    
+    quantity_entry = ctk.CTkEntry(
+        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)
+    )
+    plus_btn.pack(side="left", padx=5, pady=5)
+    
+    def add_to_cart():
+        if not product_data:
+            messagebox.showwarning("Warning", "No product selected")
+            return
+
+        try:
+            # Validate quantity
+            quantity = quantity_var.get()
+            if not (1 <= quantity <= 99):
+                messagebox.showwarning("Warning", "Please select a valid quantity (1-99).")
+                return
+
+            # Prepare data for API call
+            headers = {"Authorization": f"Bearer {token}"}
+            data = {
+                "product_id": product_data.get("id"),
+                "quantity": quantity
+            }
+
+            # Make API call
+            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
+                switch_func("user_orders")
+            else:
+                error_msg = response.json().get("detail", "Unknown error occurred")
+                messagebox.showerror("Error", f"Failed to add to cart: {error_msg}")
+        
+        except requests.RequestException as e:
+            messagebox.showerror("Error", f"Network error: {str(e)}")
+        except Exception as e:
+            messagebox.showerror("Error", f"An unexpected error occurred: {str(e)}")
+
+    # Add to cart button
+    add_to_cart_btn = ctk.CTkButton(
+        right_column,
+        text="Add to Cart",
+        font=("Helvetica", 16, "bold"),
+        height=45,
+        fg_color="#ff4242",
+        hover_color="#ff6b6b",
+        command=add_to_cart,
+        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")
+    )
+    description_label.pack(anchor="w", pady=(20, 10))
+    
+    description_text = ctk.CTkTextbox(
+        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
diff --git a/app/frontend/components/user_payments.py b/app/frontend/components/user_payments.py
index 9b8e414..a3506ae 100644
--- a/app/frontend/components/user_payments.py
+++ b/app/frontend/components/user_payments.py
@@ -100,8 +100,17 @@ def user_payments_frame(parent, switch_func, API_URL, token):
     content_frame = ctk.CTkFrame(main_section, fg_color=CARD_BG, corner_radius=15)
     content_frame.pack(side="left", fill="both", expand=True, padx=(0, 20), pady=20)
 
+    # Create a scrollable frame for the content
+    content_scrollable = ctk.CTkScrollableFrame(
+        content_frame,
+        fg_color="transparent",
+        scrollbar_button_color=SHOPPING,
+        scrollbar_button_hover_color="#0096ff"
+    )
+    content_scrollable.pack(fill="both", expand=True)
+
     # Header
-    header_frame = ctk.CTkFrame(content_frame, fg_color="transparent")
+    header_frame = ctk.CTkFrame(content_scrollable, fg_color="transparent")
     header_frame.pack(fill="x", padx=30, pady=20)
 
     title_label = ctk.CTkLabel(
@@ -121,10 +130,313 @@ def user_payments_frame(parent, switch_func, API_URL, token):
     subtitle_label.pack(anchor="w", pady=(5, 0))
 
     # Main Content
-    main_content = ctk.CTkFrame(content_frame, fg_color="transparent")
+    main_content = ctk.CTkFrame(content_scrollable, fg_color="transparent")
     main_content.pack(fill="both", expand=True, padx=30, pady=(0, 20))
 
-    # Payment Form
+    # Saved Payment Methods Section
+    saved_payments_frame = ctk.CTkFrame(main_content, fg_color="transparent")
+    saved_payments_frame.pack(fill="x", pady=(0, 40))
+
+    saved_payments_label = ctk.CTkLabel(
+        saved_payments_frame,
+        text="Current Payment Method",
+        font=("Helvetica", 18, "bold"),
+        text_color="white",
+    )
+    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.pack(expand=True)
+    saved_card_section.pack_propagate(False)
+    saved_card_section.grid_columnconfigure(0, weight=1)
+    saved_card_section.grid_columnconfigure(1, weight=0)
+
+    # Container for the saved payment card
+    saved_card_container = ctk.CTkFrame(saved_card_section, fg_color="transparent")
+    saved_card_container.grid(row=0, column=0, sticky="e", padx=(0, 20))
+
+    # Create the saved card frame (initially empty)
+    saved_card_frame = None
+
+    # Buttons container
+    buttons_container = ctk.CTkFrame(saved_card_section, fg_color="transparent")
+    buttons_container.grid(row=0, column=1, sticky="w", pady=(60, 0))
+
+    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()
+            saved_card_frame = None
+
+        # If no payment data, don't create a new card
+        if not payment:
+            return
+
+        # Create new card frame
+        saved_card_frame = ctk.CTkFrame(
+            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()
+        if card_type == "VISA":
+            saved_card_frame.configure(fg_color="#0066cc")
+        elif card_type == "MASTERCARD":
+            saved_card_frame.configure(fg_color="#EB001B")
+        elif card_type == "PAYPAL":
+            saved_card_frame.configure(fg_color="#003087")
+
+        # Chip image
+        chip_frame = ctk.CTkFrame(
+            saved_card_frame,
+            width=50,
+            height=40,
+            fg_color="#FFD700",
+            corner_radius=5
+        )
+        chip_frame.place(x=30, y=50)
+
+        # Card type label
+        card_type_label = ctk.CTkLabel(
+            saved_card_frame,
+            text=card_type,
+            font=("Helvetica", 20, "bold"),
+            text_color="white"
+        )
+        card_type_label.place(x=270, y=20)
+
+        # Format card number with spaces
+        raw_number = payment['card_number']
+        formatted_number = ' '.join([raw_number[i:i+4] for i in range(0, len(raw_number), 4)])
+
+        # Card number
+        card_number = ctk.CTkLabel(
+            saved_card_frame,
+            text=formatted_number,
+            font=("Courier", 24, "bold"),
+            text_color="white"
+        )
+        card_number.place(x=30, y=100)
+
+        # Expiry date label
+        expiry_label = ctk.CTkLabel(
+            saved_card_frame,
+            text="VALID\nTHRU",
+            font=("Helvetica", 8),
+            text_color="white"
+        )
+        expiry_label.place(x=30, y=140)
+
+        # Expiry date
+        expiry_date = ctk.CTkLabel(
+            saved_card_frame,
+            text=payment['expiry_date'],
+            font=("Helvetica", 14, "bold"),
+            text_color="white"
+        )
+        expiry_date.place(x=30, y=165)
+
+        # Cardholder name
+        cardholder_name = ctk.CTkLabel(
+            saved_card_frame,
+            text="CARDHOLDER NAME",
+            font=("Helvetica", 14, "bold"),
+            text_color="white"
+        )
+        cardholder_name.place(x=150, y=165)
+
+        # Add buttons with improved styling
+        edit_button = ctk.CTkButton(
+            buttons_container,
+            text="Edit",
+            width=120,
+            height=38,
+            corner_radius=20,
+            command=lambda: edit_payment(payment),
+            fg_color="#00B2FF",
+            hover_color="#0095D9",
+            font=("Helvetica", 14, "bold"),
+            border_width=2,
+            border_color="#80D9FF"
+        )
+        edit_button.pack(pady=(0, 15))
+
+        # Delete button with matching style
+        delete_button = ctk.CTkButton(
+            buttons_container,
+            text="Delete",
+            width=120,
+            height=38,
+            corner_radius=20,
+            command=lambda: delete_payment(payment),
+            fg_color="#FF4444",
+            hover_color="#D93939",
+            font=("Helvetica", 14, "bold"),
+            border_width=2,
+            border_color="#FFA4A4"
+        )
+        delete_button.pack()
+
+    def disable_form_inputs():
+        """Disable all form inputs"""
+        card_entry.configure(state="disabled")
+        month_entry.configure(state="disabled")
+        year_entry.configure(state="disabled")
+        cvv_entry.configure(state="disabled")
+        card_type_dropdown.configure(state="disabled")
+        save_button.configure(state="disabled")
+
+    def enable_form_inputs():
+        """Enable all form inputs"""
+        card_entry.configure(state="normal")
+        month_entry.configure(state="normal")
+        year_entry.configure(state="normal")
+        cvv_entry.configure(state="normal")
+        card_type_dropdown.configure(state="normal")
+        save_button.configure(state="normal")
+
+    def clear_form():
+        """Clear all form fields and reset to default state"""
+        card_entry.delete(0, "end")
+        month_entry.delete(0, "end")
+        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')
+        update_card_preview()
+        # Enable form inputs when clearing
+        enable_form_inputs()
+
+    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']
+            
+            # Populate form with existing payment data
+            card_entry.delete(0, "end")
+            card_entry.insert(0, payment['card_number'])
+            
+            # Set card type (capitalize first letter)
+            card_type = payment['payment_method'].capitalize()
+            card_type_var.set(card_type)
+            
+            # Set expiry date
+            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']
+            }
+            create_payment_card(temp_payment)
+        except Exception as e:
+            messagebox.showerror("Error", f"Failed to load payment data: {str(e)}")
+
+    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?"):
+            try:
+                headers = {"Authorization": f"Bearer {frame.token}"}
+                response = requests.delete(
+                    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")
+                    
+                    # 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")
+                    messagebox.showerror("Error", error_msg)
+            except Exception as 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])
+                    # Disable form inputs when payment exists
+                    disable_form_inputs()
+                else:
+                    # If no payment methods, clear everything and enable form
+                    clear_form()
+                    enable_form_inputs()
+            else:
+                messagebox.showerror("Error", "Failed to fetch payment method")
+        except Exception as e:
+            messagebox.showerror("Error", f"Failed to fetch payment method: {str(e)}")
+
+    # Payment Form - Move this section up before it's used
     form_frame = ctk.CTkFrame(main_content, fg_color="transparent")
     form_frame.pack(fill="x", pady=(0, 20))
 
@@ -148,7 +460,7 @@ def user_payments_frame(parent, switch_func, API_URL, token):
     )
     card_type_label.pack(anchor="w", pady=(0, 5))
 
-    card_type_var = ctk.StringVar(value="visa")
+    card_type_var = ctk.StringVar(value="Visa")
     card_type_options = ["Visa", "Mastercard", "PayPal"]
 
     card_type_dropdown = ctk.CTkOptionMenu(
@@ -209,6 +521,86 @@ def user_payments_frame(parent, switch_func, API_URL, token):
     )
     cvv_entry.pack(anchor="w")
 
+    # Card Preview Section
+    preview_frame = ctk.CTkFrame(form_frame, fg_color="transparent")
+    preview_frame.pack(fill="x", pady=(20, 10))
+
+    preview_label = ctk.CTkLabel(
+        preview_frame,
+        text="Card Preview",
+        font=("Helvetica", 14),
+        text_color="white"
+    )
+    preview_label.pack(anchor="center")
+
+    # Card Preview Frame - Centered
+    preview_container = ctk.CTkFrame(preview_frame, fg_color="transparent")
+    preview_container.pack(fill="x", pady=(0, 20))
+
+    card_preview = ctk.CTkFrame(
+        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
+    )
+    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_type_preview.place(x=270, y=20)
+
+    # Card number preview
+    card_number_preview = ctk.CTkLabel(
+        card_preview,
+        text="**** **** **** ****",
+        font=("Courier", 24, "bold"),
+        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"
+    )
+    expiry_label.place(x=30, y=140)
+
+    expiry_preview = ctk.CTkLabel(
+        card_preview,
+        text="MM/YY",
+        font=("Helvetica", 14, "bold"),
+        text_color="white"
+    )
+    expiry_preview.place(x=30, y=165)
+
+    # Cardholder name preview
+    cardholder_preview = ctk.CTkLabel(
+        card_preview,
+        text="CARDHOLDER NAME",
+        font=("Helvetica", 14, "bold"),
+        text_color="white"
+    )
+    cardholder_preview.place(x=150, y=165)
+
     def validate_card_number(event=None):
         value = card_entry.get().replace(" ", "")
         if not value.isdigit():
@@ -248,19 +640,68 @@ def user_payments_frame(parent, switch_func, API_URL, token):
         if len(value) > 3:
             cvv_entry.delete(3, "end")
 
+    def update_card_preview(event=None):
+        # Update card number
+        card_num = card_entry.get().strip()
+        if card_num:
+            # Format card number with spaces
+            formatted_num = ' '.join([card_num[i:i+4] if i+4 <= len(card_num) else '*'*(4-(len(card_num)-i)) 
+                                    for i in range(0, 16, 4)])
+            card_number_preview.configure(text=formatted_num)
+        else:
+            card_number_preview.configure(text="**** **** **** ****")
+
+        # Update expiry date
+        month = month_entry.get().strip()
+        year = year_entry.get().strip()
+        if month or year:
+            expiry_preview.configure(text=f"{month.zfill(2)}/{year.zfill(2)}")
+        else:
+            expiry_preview.configure(text="MM/YY")
+
+        # 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")
+        elif card_type == "MASTERCARD":
+            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'):
+            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"
+            }
+            create_payment_card(temp_payment)
+
     # Bind validation functions
     card_entry.bind("<KeyRelease>", validate_card_number)
     month_entry.bind("<KeyRelease>", validate_month)
     year_entry.bind("<KeyRelease>", validate_year)
     cvv_entry.bind("<KeyRelease>", validate_cvv)
 
+    # Bind the update function to all relevant widgets
+    card_entry.bind("<KeyRelease>", update_card_preview)
+    month_entry.bind("<KeyRelease>", update_card_preview)
+    year_entry.bind("<KeyRelease>", update_card_preview)
+    card_type_dropdown.bind("<KeyRelease>", update_card_preview)
+
     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()
         year = year_entry.get().strip()
         cvv = cvv_entry.get().strip()
-        card_type = card_type_var.get().lower()  # Convert to lowercase to match backend expectations
+        card_type = card_type_var.get().lower()
 
         if not all([card_num, month, year, cvv]):
             messagebox.showerror("Error", "Please fill in all fields")
@@ -290,22 +731,42 @@ def user_payments_frame(parent, switch_func, API_URL, token):
             "cvv": cvv
         }
 
-        # Use the API function to save payment
-        status_code, response = add_payment_method(API_URL, frame.token, payload)
-        
-        if status_code == 200:
-            messagebox.showinfo("Success", "Payment method added successfully!")
-            # Clear form
-            card_entry.delete(0, "end")
-            month_entry.delete(0, "end")
-            year_entry.delete(0, "end")
-            cvv_entry.delete(0, "end")
-            card_type_var.set("Visa")  # Reset to default option
-        else:
-            error_msg = "Failed to add payment method"
-            if isinstance(response, dict) and "detail" in response:
-                error_msg = response["detail"]
-            messagebox.showerror("Error", error_msg)
+        try:
+            headers = {"Authorization": f"Bearer {frame.token}"}
+            
+            if hasattr(frame, 'current_payment_id'):
+                # Update existing payment
+                response = requests.put(
+                    f"{API_URL}/payment/{frame.current_payment_id}",
+                    headers=headers,
+                    json=payload
+                )
+                action = "updated"
+            else:
+                # Add new payment
+                response = requests.post(
+                    f"{API_URL}/payment/add",
+                    headers=headers,
+                    json=payload
+                )
+                action = "added"
+
+            if response.status_code == 200:
+                messagebox.showinfo("Success", f"Payment method {action} successfully!")
+                # 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')
+                # 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")
+                messagebox.showerror("Error", error_msg)
+        except Exception as e:
+            messagebox.showerror("Error", f"Failed to save payment method: {str(e)}")
 
     # Save Button
     save_button = ctk.CTkButton(
@@ -320,4 +781,7 @@ def user_payments_frame(parent, switch_func, API_URL, token):
     )
     save_button.pack(fill="x", pady=(30, 0))
 
+    # Initial refresh of payment methods
+    refresh_payments()
+
     return frame
diff --git a/app/frontend/main.py b/app/frontend/main.py
index a0de2d4..6a4b832 100644
--- a/app/frontend/main.py
+++ b/app/frontend/main.py
@@ -4,6 +4,7 @@ from components.auth.register import register_frame
 from components.shop.create_shop import create_shop_frame
 from components.shop.view_shop import view_shop_frame
 from components.product.create_product import create_product_frame
+from components.product.view_product import view_product_frame
 from components.admin.category import category_frame
 from components.dashboard import dashboard_frame
 from components.user_details import user_details_frame
@@ -54,6 +55,7 @@ create_shop = create_shop_frame(
 # 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
 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)
 dashboard = dashboard_frame(root, switch_frame, API_URL, access_token)
 user_details = user_details_frame(root, switch_frame, API_URL, access_token)
@@ -65,6 +67,7 @@ frames = {
     "register": register,
     "create_shop": create_shop,
     "create_product": product,
+    "view_product": view_product,
     "category": category,
     "view_shop": view_shop,
     "dashboard": dashboard,
-- 
GitLab