diff --git a/app/backend/routes/search.py b/app/backend/routes/search.py index 1c5999d77e9c19cfbdaf31ac5b4de06c61ab4e92..48d27292c4a62c82f07e4fbaf92cab80b29849be 100644 --- a/app/backend/routes/search.py +++ b/app/backend/routes/search.py @@ -7,7 +7,7 @@ from typing import List, Union router = APIRouter() -@router.get("/search", response_model=List[Union[Shop, Product]]) +@router.get("/", response_model=List[Union[Shop, Product]]) def search( name: str = Query(None, description="Name to search"), category: str = Query(None, description="Category to filter by (for products)"), diff --git a/app/backend/routes/shop.py b/app/backend/routes/shop.py index e6f364d49b56ec0afb917e0361fa8014aa2ca0c1..88088c11741124e2a0db9b3231e33e6dfe6ef644 100644 --- a/app/backend/routes/shop.py +++ b/app/backend/routes/shop.py @@ -17,11 +17,11 @@ def create_shop( description: str = Form(None), file: UploadFile = File(None), session: Session = Depends(get_session), - # current_user: User = Depends(get_current_user), - owner_id=int, + current_user: User = Depends(get_current_user), + # owner_id=int, ): - # shop = ShopCreate(name=name, description=description, owner_id=current_user.id) - shop = ShopCreate(name=name, description=description, owner_id=owner_id) + shop = ShopCreate(name=name, description=description, owner_id=current_user.id) + # shop = ShopCreate(name=name, description=description, owner_id=owner_id) db_shop = Shop.from_orm(shop) session.add(db_shop) diff --git a/app/frontend/components/auth/login.py b/app/frontend/components/auth/login.py index 1c7baeb1a6c9a3502caa178c898b74150f19f54f..87b5082ff5682aa992a9ce58138dc956d55d6485 100644 --- a/app/frontend/components/auth/login.py +++ b/app/frontend/components/auth/login.py @@ -1,7 +1,6 @@ import customtkinter as ctk from tkinter import messagebox -import requests - +from utils.api_requests import login_api # Import the login function from login_api.py def login_frame(parent, switch_func, API_URL): frame = ctk.CTkFrame(parent) @@ -14,23 +13,16 @@ def login_frame(parent, switch_func, API_URL): messagebox.showwarning("Input Error", "Both fields are required!") return - response = requests.post( - f"{API_URL}/auth/login", json={"email": email, "password": password} - ) - - try: - response_data = response.json() - if response.status_code == 200: - access_token = response_data["access_token"] - messagebox.showinfo("Login Successful", f"Welcome back, {email}!") - 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") - ) - except requests.exceptions.JSONDecodeError: - messagebox.showerror("Login Failed", "Server returned an invalid response.") + # Call the API function from login_api.py + status_code, response_data = login_api(email, password, 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) + print(f"Access Token in login: {access_token}") # Debugging line + else: + messagebox.showerror("Login Failed", response_data.get("detail", "Invalid credentials")) ctk.CTkLabel(frame, text="Login", font=("Helvetica", 18, "bold")).pack(pady=10) diff --git a/app/frontend/components/auth/register.py b/app/frontend/components/auth/register.py index e09813251f354b657e5adcf5ca88ca3d747cfadb..01b1b75b4b65ee43b33c2bbea1faea42cfbcfb01 100644 --- a/app/frontend/components/auth/register.py +++ b/app/frontend/components/auth/register.py @@ -1,7 +1,6 @@ import customtkinter as ctk from tkinter import messagebox -import requests - +from utils.api_requests import register_api # Import the API function def register_frame(parent, switch_func, API_URL): frame = ctk.CTkFrame(parent) @@ -13,13 +12,7 @@ def register_frame(parent, switch_func, API_URL): password = entry_password.get() confirm_password = entry_confirm_password.get() - if ( - not username - or not email - or not phone_number - or not password - or not confirm_password - ): + if not username or not email or not phone_number or not password or not confirm_password: messagebox.showwarning("Input Error", "All fields are required!") return @@ -27,29 +20,14 @@ def register_frame(parent, switch_func, API_URL): messagebox.showerror("Password Error", "Passwords do not match!") return - response = requests.post( - f"{API_URL}/auth/signup", - json={ - "username": username, - "email": email, - "phone_number": phone_number, - "password": password, - }, - ) + # Call the API function from register_api.py + status_code, response_data = register_api(username, email, phone_number, password, API_URL) - try: - response_data = response.json() - if response.status_code == 200: - messagebox.showinfo("Registration Successful", f"Welcome, {username}!") - switch_func("login") - else: - messagebox.showerror( - "Registration Failed", response_data.get("detail", "Unknown error") - ) - except requests.exceptions.JSONDecodeError: - messagebox.showerror( - "Registration Failed", "Server returned an invalid response." - ) + if status_code == 200: + messagebox.showinfo("Registration Successful", f"Welcome, {username}!") + switch_func("login") + else: + messagebox.showerror("Registration Failed", response_data.get("detail", "Unknown error")) ctk.CTkLabel(frame, text="Register", font=("Helvetica", 18, "bold")).pack(pady=10) diff --git a/app/frontend/components/shop/create_shop.py b/app/frontend/components/shop/create_shop.py index 44d4d79a66ee3cdb39d263915ce81b2131ae50af..ed6ba4bbee4ba60f58f4a2ad08e6289a82caec60 100644 --- a/app/frontend/components/shop/create_shop.py +++ b/app/frontend/components/shop/create_shop.py @@ -1,7 +1,7 @@ import customtkinter as ctk from tkinter import messagebox, filedialog -import requests import os +from utils.api_requests import create_shop_api # Import API function def create_shop_frame(parent, switch_func, API_URL, token): frame = ctk.CTkFrame(parent) @@ -31,46 +31,17 @@ def create_shop_frame(parent, switch_func, API_URL, token): messagebox.showwarning("Input Error", "Shop name is required!") return - url = f"{API_URL}/shops" - data = {"name": name, "description": description} - files = {} - - if selected_file_path[0]: - try: - files["file"] = open(selected_file_path[0], "rb") - except Exception as e: - messagebox.showerror("File Error", f"Unable to open file: {str(e)}") - return - - if not frame.access_token: - messagebox.showerror( - "Token Error", "Access token not found. Please log in." - ) - return + # Call the API function from shop_api.py + status_code, response_data = create_shop_api( + name, description, selected_file_path[0], API_URL, frame.access_token + ) + + if status_code == 200: + messagebox.showinfo("Shop Created", f"Shop '{name}' created successfully!") + else: + messagebox.showerror("Error", response_data.get("detail", "An error occurred")) - headers = {"Authorization": f"Bearer {frame.access_token}"} - print(f"Access Token in create_shop: {frame.access_token}") # Debugging line - - try: - response = requests.post(url, data=data, files=files, headers=headers) - if "file" in files: - files["file"].close() - - response_data = response.json() - if response.status_code == 200: - messagebox.showinfo( - "Shop Created", f"Shop '{name}' created successfully!" - ) - else: - messagebox.showerror( - "Error", response_data.get("detail", "An error occurred") - ) - except Exception as e: - messagebox.showerror("Request Error", str(e)) - - ctk.CTkLabel(frame, text="Create Shop", font=("Helvetica", 18, "bold")).pack( - pady=10 - ) + ctk.CTkLabel(frame, text="Create Shop", font=("Helvetica", 18, "bold")).pack(pady=10) ctk.CTkLabel(frame, text="Shop Name:").pack(pady=5) entry_name = ctk.CTkEntry(frame, placeholder_text="Enter shop name") @@ -84,15 +55,10 @@ def create_shop_frame(parent, switch_func, API_URL, token): file_label = ctk.CTkLabel(frame, text="No file selected") file_label.pack(pady=5) - ctk.CTkButton( - frame, text="Create Shop", fg_color="green", command=create_shop - ).pack(pady=15) + ctk.CTkButton(frame, text="Create Shop", fg_color="green", command=create_shop).pack(pady=15) ctk.CTkButton( - frame, - text="Back", - fg_color="transparent", - command=lambda: switch_func("login"), + frame, text="Back", fg_color="transparent", command=lambda: switch_func("login") ).pack(pady=5) - return frame \ No newline at end of file + return frame diff --git a/app/frontend/components/shop/view_shop.py b/app/frontend/components/shop/view_shop.py index e7aa1583c30e38415253056ba8e7ed16b370d531..2c106654d7517b5a7d2942b279a80e0331456f33 100644 --- a/app/frontend/components/shop/view_shop.py +++ b/app/frontend/components/shop/view_shop.py @@ -1,9 +1,7 @@ import customtkinter as ctk -import requests from tkinter import messagebox -from PIL import Image, ImageTk -import io - +from PIL import ImageTk +from utils.api_requests import fetch_shop_details, fetch_shop_products, load_image_from_url def view_shop_frame(parent, switch_func, API_URL, token): frame = ctk.CTkFrame(parent) @@ -13,14 +11,10 @@ def view_shop_frame(parent, switch_func, API_URL, token): title_label.pack(pady=10) # Shop Details - shop_name_label = ctk.CTkLabel( - frame, text="Shop Name: ", font=("Helvetica", 14, "bold") - ) + shop_name_label = ctk.CTkLabel(frame, text="Shop Name: ", font=("Helvetica", 14, "bold")) shop_name_label.pack(pady=5) - shop_description_label = ctk.CTkLabel( - frame, text="Description: ", font=("Helvetica", 12) - ) + shop_description_label = ctk.CTkLabel(frame, text="Description: ", font=("Helvetica", 12)) shop_description_label.pack(pady=5) shop_image_label = ctk.CTkLabel(frame, text="") # Placeholder for shop image @@ -30,63 +24,39 @@ def view_shop_frame(parent, switch_func, API_URL, token): product_list_frame = ctk.CTkFrame(frame) product_list_frame.pack(fill="both", expand=True, padx=10, pady=10) - def fetch_shop_data(): - """Fetch the shop created by the logged-in user""" - headers = {"Authorization": f"Bearer {token}"} - try: - response = requests.get( - f"{API_URL}/shops/1", headers=headers - ) # Adjust the endpoint as needed - if response.status_code == 200: - shop_data = response.json() - shop_name_label.configure(text=f"Shop Name: {shop_data['name']}") - shop_description_label.configure( - text=f"Description: {shop_data.get('description', 'No description')}" - ) - - # Load and display shop image if available - if "image_url" in shop_data and shop_data["image_url"]: - try: - image_response = requests.get(shop_data["image_url"]) - image = Image.open(io.BytesIO(image_response.content)) - image = image.resize((150, 150)) - img_tk = ImageTk.PhotoImage(image) - - shop_image_label.configure(image=img_tk, text="") - shop_image_label.image = img_tk - except Exception: - pass - - fetch_products(shop_data["id"]) # Fetch products for this shop - else: - messagebox.showerror("Error", "Failed to fetch shop details.") - except Exception as e: - messagebox.showerror("Error", f"Request error: {e}") - - def fetch_products(shop_id): - """Fetch products that belong to the user's shop""" - headers = {"Authorization": f"Bearer {token}"} - try: - response = requests.get( - f"{API_URL}/products?shop_id={shop_id}", headers=headers - ) - if response.status_code == 200: - products = response.json() - display_products(products) - else: - messagebox.showerror("Error", "Failed to fetch products.") - except Exception as e: - messagebox.showerror("Error", f"Request error: {e}") - - def display_products(products): - """Display the list of products in the shop""" + def fetch_and_display_shop(): + """Fetch shop details and update UI""" + shop_data = fetch_shop_details(API_URL, token) + + if "error" in shop_data: + messagebox.showerror("Error", shop_data["error"]) + return + + shop_name_label.configure(text=f"Shop Name: {shop_data['name']}") + shop_description_label.configure(text=f"Description: {shop_data.get('description', 'No description')}") + + if "image_url" in shop_data and shop_data["image_url"]: + image = load_image_from_url(shop_data["image_url"]) + if image: + img_tk = ImageTk.PhotoImage(image) + shop_image_label.configure(image=img_tk, text="") + shop_image_label.image = img_tk # Prevent garbage collection + + fetch_and_display_products(shop_data["id"]) + + def fetch_and_display_products(shop_id): + """Fetch products and display them in the UI""" + products = fetch_shop_products(API_URL, token, shop_id) + + if "error" in products: + messagebox.showerror("Error", products["error"]) + return + for widget in product_list_frame.winfo_children(): widget.destroy() if not products: - ctk.CTkLabel( - product_list_frame, text="No products found.", font=("Helvetica", 12) - ).pack(pady=10) + ctk.CTkLabel(product_list_frame, text="No products found.", font=("Helvetica", 12)).pack(pady=10) return for product in products: @@ -97,44 +67,30 @@ def view_shop_frame(parent, switch_func, API_URL, token): img_label = ctk.CTkLabel(product_frame, text="") # Placeholder img_label.pack(side="left", padx=5) - # Load and display product image if "images" in product and product["images"]: image_url = product["images"][0]["image_url"] - try: - image_response = requests.get(image_url) - image = Image.open(io.BytesIO(image_response.content)) - image = image.resize((50, 50)) + image = load_image_from_url(image_url, size=(50, 50)) + if image: img_tk = ImageTk.PhotoImage(image) - img_label.configure(image=img_tk, text="") - img_label.image = img_tk - except Exception: - pass + img_label.image = img_tk # Prevent garbage collection # Product Details details_frame = ctk.CTkFrame(product_frame) details_frame.pack(side="left", fill="x", expand=True, padx=10) - ctk.CTkLabel( - details_frame, text=product["name"], font=("Helvetica", 12, "bold") - ).pack(anchor="w") - ctk.CTkLabel( - details_frame, - text=f"Price: ${product['price']:.2f}", - font=("Helvetica", 12), - ).pack(anchor="w") - - # Refresh Data Button - refresh_button = ctk.CTkButton(frame, text="Refresh", command=fetch_shop_data) + ctk.CTkLabel(details_frame, text=product["name"], font=("Helvetica", 12, "bold")).pack(anchor="w") + ctk.CTkLabel(details_frame, text=f"Price: ${product['price']:.2f}", font=("Helvetica", 12)).pack(anchor="w") + + # Refresh Button + refresh_button = ctk.CTkButton(frame, text="Refresh", command=fetch_and_display_shop) refresh_button.pack(pady=10) # Back Button - back_button = ctk.CTkButton( - frame, text="Back", command=lambda: switch_func("login") - ) + back_button = ctk.CTkButton(frame, text="Back", command=lambda: switch_func("login")) back_button.pack(pady=10) # Fetch shop data on load - fetch_shop_data() + fetch_and_display_shop() - return frame \ No newline at end of file + return frame diff --git a/app/frontend/utils/api_requests.py b/app/frontend/utils/api_requests.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..4f50771d7fd4a12c94986dd217f248d6083931fc 100644 --- a/app/frontend/utils/api_requests.py +++ b/app/frontend/utils/api_requests.py @@ -0,0 +1,139 @@ +import requests +from PIL import Image +import io + +#Login API +def login_api(email, password, api_url): + """ + Sends login request to the API. + + :param email: User's email + :param password: User's password + :param api_url: Base API URL + :return: Tuple (status_code, response_data) + """ + try: + response = requests.post(f"{api_url}/auth/login", json={"email": email, "password": password}) + response.raise_for_status() # Raise an error for 4xx and 5xx responses + return response.status_code, response.json() # Return status and response data + except requests.exceptions.RequestException as e: + return None, {"error": str(e)} # Return error message if request fails + +#Register API +def register_api(username, email, phone_number, password, api_url): + """ + Sends a registration request to the API. + + :param username: User's username + :param email: User's email + :param phone_number: User's phone number + :param password: User's password + :param api_url: Base API URL + :return: Tuple (status_code, response_data) + """ + try: + response = requests.post( + f"{api_url}/auth/signup", + json={ + "username": username, + "email": email, + "phone_number": phone_number, + "password": password, + }, + ) + response.raise_for_status() # Raise an error for HTTP errors (4xx, 5xx) + return response.status_code, response.json() # Return response data + except requests.exceptions.RequestException as e: + return None, {"detail": str(e)} # Return error message if request fails + +#Create Shop +def create_shop_api(name, description, file_path, api_url, access_token): + """ + Sends a request to create a shop with optional image upload. + + :param name: Name of the shop + :param description: Shop description + :param file_path: Path to the shop image (optional) + :param api_url: Base API URL + :param access_token: User's access token + :return: Tuple (status_code, response_data) + """ + if not access_token: + return None, {"detail": "Access token not found. Please log in."} + + url = f"{api_url}/shops" + data = {"name": name, "description": description} + headers = {"Authorization": f"Bearer {access_token}"} + files = {} + + if file_path: + try: + files["file"] = open(file_path, "rb") + except Exception as e: + return None, {"detail": f"Unable to open file: {str(e)}"} + + try: + response = requests.post(url, data=data, files=files, headers=headers) + if "file" in files: + files["file"].close() + + return response.status_code, response.json() + except requests.exceptions.RequestException as e: + return None, {"detail": str(e)} + +#View Shop +def fetch_shop_details(api_url, token, shop_id=1): + """ + Fetches details of the shop owned by the logged-in user. + + :param api_url: Base API URL + :param token: Authorization token + :param shop_id: ID of the shop (default = 1) + :return: Dictionary containing shop details or error message + """ + headers = {"Authorization": f"Bearer {token}"} + try: + response = requests.get(f"{api_url}/shops/{shop_id}", headers=headers) + if response.status_code == 200: + return response.json() + else: + return {"error": "Failed to fetch shop details"} + except requests.exceptions.RequestException as e: + return {"error": str(e)} + +#Fetch Products +def fetch_shop_products(api_url, token, shop_id): + """ + Fetches products for a given shop. + + :param api_url: Base API URL + :param token: Authorization token + :param shop_id: ID of the shop + :return: List of products or error message + """ + headers = {"Authorization": f"Bearer {token}"} + try: + response = requests.get(f"{api_url}/products?shop_id={shop_id}", headers=headers) + if response.status_code == 200: + return response.json() + else: + return {"error": "Failed to fetch products"} + except requests.exceptions.RequestException as e: + return {"error": str(e)} + +#Load Image from URL +def load_image_from_url(image_url, size=(150, 150)): + """ + Loads and resizes an image from a given URL. + + :param image_url: URL of the image + :param size: Tuple indicating image size + :return: PIL ImageTk object or None + """ + try: + image_response = requests.get(image_url) + image = Image.open(io.BytesIO(image_response.content)) + image = image.resize(size) + return image + except Exception: + return None \ No newline at end of file diff --git a/app/static/shop_Shop1/product_string/Anime-Girl4.png b/app/static/shop_Shop1/product_string/Anime-Girl4.png new file mode 100644 index 0000000000000000000000000000000000000000..34d06a19ed12166f08fc64887f47c7f8c7ce404d Binary files /dev/null and b/app/static/shop_Shop1/product_string/Anime-Girl4.png differ diff --git a/app/static/shop_abc/404-page_not_found.png b/app/static/shop_abc/404-page_not_found.png new file mode 100644 index 0000000000000000000000000000000000000000..de48d6565203a4919333e72e1d72d0a2251a84fe Binary files /dev/null and b/app/static/shop_abc/404-page_not_found.png differ diff --git a/app/static/shop_awdfasd/404-page_not_found.png b/app/static/shop_awdfasd/404-page_not_found.png new file mode 100644 index 0000000000000000000000000000000000000000..de48d6565203a4919333e72e1d72d0a2251a84fe Binary files /dev/null and b/app/static/shop_awdfasd/404-page_not_found.png differ diff --git a/app/static/shop_string/Anime-Girl4.png b/app/static/shop_string/Anime-Girl4.png new file mode 100644 index 0000000000000000000000000000000000000000..34d06a19ed12166f08fc64887f47c7f8c7ce404d Binary files /dev/null and b/app/static/shop_string/Anime-Girl4.png differ