diff --git a/UI_V2/Dockerfile b/UI_V2/Dockerfile index 4d28573..67d2c27 100644 --- a/UI_V2/Dockerfile +++ b/UI_V2/Dockerfile @@ -22,10 +22,12 @@ RUN mkdir -p /app/instance /app/assets # Default port ENV FLET_PORT=8080 -EXPOSE 8080 +# Copy the entrypoint script and make it executable +COPY entrypoint.sh . +RUN chmod +x entrypoint.sh -# Health check -HEALTHCHECK --interval=30s --timeout=3s --retries=3 CMD curl -fsS http://127.0.0.1:8080/ || exit 1 +# Expose both ports (Flet 8080 and Flask 9000) +EXPOSE 8080 9000 -# Run entrypoint to create superuser and start the app -ENTRYPOINT ["sh", "-c", "python create_super_user.py && python main.py"] \ No newline at end of file +# Use the script to start the container +ENTRYPOINT ["./entrypoint.sh"] \ No newline at end of file diff --git a/UI_V2/admin/admin_chat.py b/UI_V2/admin/admin_chat.py index c42eb16..dfb1462 100644 --- a/UI_V2/admin/admin_chat.py +++ b/UI_V2/admin/admin_chat.py @@ -38,6 +38,15 @@ class AdminChat: on_click=self.on_send_click, ) + # Notification sound to be played on new client messages + # Make sure the file path points to a valid audio file in your assets. + # Example: place `chat_notify.mp3` in `assets/sounds/`. + self.notification_audio = ft.Audio( + src="assets/sounds/chat_notify_triple.mp3", + autoplay=True, + volume=1.0, + ) + # Subscribe to PubSub for live updates from clients try: self.page.pubsub.subscribe(self.on_pubsub_message) @@ -107,7 +116,19 @@ class AdminChat: vertical_alignment=ft.CrossAxisAlignment.CENTER, controls=[ self.header_label, - ft.Icon(ft.Icons.SUPPORT_AGENT), + ft.Row( + [ + ft.Icon(ft.Icons.SUPPORT_AGENT), + ft.WebView( + url="/enable-notifications", # small HTML page with a single 'Activează notificările' button + width=160, + height=40, + expand=False, + ), + ], + vertical_alignment=ft.CrossAxisAlignment.CENTER, + spacing=8, + ), ], ), ft.Divider(), @@ -120,6 +141,7 @@ class AdminChat: vertical_alignment=ft.CrossAxisAlignment.CENTER, controls=[self.input_field, self.send_button], ), + self.notification_audio, ], ), ) @@ -165,7 +187,7 @@ class AdminChat: title = f"Chat #{chat_id}" if user_id: user = self.user_namager.get(user_id) - title += f" • User {user['name'].replace("~", " ")}" + title += f" • User {user['name'].replace('~', ' ')}" subtitle_parts = [] if status == "open": @@ -232,7 +254,7 @@ class AdminChat: label = f"Chat #{chat_id}" if user_id: user = self.user_namager.get(user_id) - label += f" • User {user['name'].replace("~", " ")}" + label += f" • User {user['name'].replace('~', ' ')}" self.header_label.value = label else: self.header_label.value = f"Chat #{chat_id}" @@ -362,6 +384,14 @@ class AdminChat: if data.get("type") == "chat_message" and data.get("sender") == "client": chat_id = data.get("chat_id") + # Play notification sound for any incoming client message + try: + print('Play sound') + self.notification_audio.play() + print('Played sound') + except Exception as e: + print(e) + # If this is the currently open chat, append bubble directly if self.active_chat_id == chat_id: text = data.get("text", "") diff --git a/UI_V2/assets/sounds/chat_notify.wav b/UI_V2/assets/sounds/chat_notify.wav new file mode 100644 index 0000000..e94e1b2 Binary files /dev/null and b/UI_V2/assets/sounds/chat_notify.wav differ diff --git a/UI_V2/assets/sounds/chat_notify_triple.wav b/UI_V2/assets/sounds/chat_notify_triple.wav new file mode 100644 index 0000000..313b380 Binary files /dev/null and b/UI_V2/assets/sounds/chat_notify_triple.wav differ diff --git a/UI_V2/chat/chat.py b/UI_V2/chat/chat.py new file mode 100644 index 0000000..61dd6fc --- /dev/null +++ b/UI_V2/chat/chat.py @@ -0,0 +1,14 @@ +import flet as ft + +class Chat: + def __init__(self, page: ft.Page): + self.page = page + + def build(self): + return ft.Container( + content=ft.Column( + [ + ft.Text("Aveti nevoie de ajutor?") + ] + ) + ) \ No newline at end of file diff --git a/UI_V2/docker-compose.yml b/UI_V2/docker-compose.yml index 189f726..cff7338 100644 --- a/UI_V2/docker-compose.yml +++ b/UI_V2/docker-compose.yml @@ -25,6 +25,7 @@ services: # SHOP_DB_PATH: "/app/instance/shop.db" # custom var expose: - "8080" + - "9000" volumes: - tg_instance:/app/instance - tg_assets:/app/assets diff --git a/UI_V2/entrypoint.sh b/UI_V2/entrypoint.sh new file mode 100644 index 0000000..b724f04 --- /dev/null +++ b/UI_V2/entrypoint.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +# 1. Run your superuser script +python create_super_user.py + +# 2. Start Flask in the background (&) +# We use & to let it run while the script continues +python flask_server.py & + +# 3. Start Flet (Main process) +# This stays in the foreground so the container doesn't exit +python main.py \ No newline at end of file diff --git a/UI_V2/flask_server.py b/UI_V2/flask_server.py new file mode 100644 index 0000000..d314822 --- /dev/null +++ b/UI_V2/flask_server.py @@ -0,0 +1,62 @@ +from __future__ import annotations + +import os +import logging +from logging.handlers import RotatingFileHandler +from flask import Flask, request, jsonify +from flask_cors import CORS + +try: + from dotenv import load_dotenv + load_dotenv() +except Exception: + pass + +from UI_V2.helpers.netopia import verify_ipn, get_status + +app = Flask(__name__) +CORS(app, resources={r"/api/*": {"origins": "*"}}) + +# ---------- Logging ---------- +app.logger.setLevel(logging.INFO) +_log_dir = os.getenv("LOG_DIR", "logs") +os.makedirs(_log_dir, exist_ok=True) +_handler = RotatingFileHandler(os.path.join(_log_dir, "netopia_api.log"), maxBytes=1_000_000, backupCount=3) +_handler.setLevel(logging.INFO) +_handler.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s")) +app.logger.addHandler(_handler) + + +@app.get("/healthz") +def healthz(): + return {"ok": True}, 200 + +@app.post("/api/payments/ipn") +def ipn(): + try: + raw = request.data + data = verify_ipn(raw) + app.logger.info("IPN OK: %s", data) + return jsonify({"ok": True, "data": data}), 200 + except Exception as e: + app.logger.exception("IPN verification failed: %s", e) + return jsonify({"ok": False, "error": str(e)}), 400 + + +@app.get("/api/payments/status") +def status(): + ntp_id = request.args.get("ntpID") + order_id = request.args.get("orderID") + try: + resp = get_status(ntp_id=ntp_id, order_id=order_id) + return jsonify({"ok": True, "data": resp}), 200 + except Exception as e: + app.logger.exception("Status query failed: %s", e) + return jsonify({"ok": False, "error": str(e)}), 400 + + +if __name__ == "__main__": + host = os.getenv("API_HOST", "0.0.0.0") + port = int(os.getenv("API_PORT", "9000")) + app.logger.info("Starting NETOPIA Flask sidecar on %s:%s", host, port) + app.run(host=host, port=port) diff --git a/UI_V2/main.py b/UI_V2/main.py index 1d9c7e1..15c089f 100644 --- a/UI_V2/main.py +++ b/UI_V2/main.py @@ -22,7 +22,7 @@ from dotenv import load_dotenv load_dotenv() def main(page: ft.Page): - page.title = "Taina Gustului" + page.title = "Taina Gustului - Condimente, nuci și fructe uscate în Craiova" page.theme_mode = ft.ThemeMode.LIGHT page.theme = ft.Theme(color_scheme=ft.ColorScheme(primary=ft.Colors.BROWN)) page.vertical_alignment = ft.MainAxisAlignment.CENTER