forked from Support/sp-hydra-veil-gui
Multiple invoices resumed, each one stops the timer on the other.
This commit is contained in:
parent
daa3815c38
commit
e071b0f502
11 changed files with 500 additions and 168 deletions
|
|
@ -1,7 +1,8 @@
|
|||
# core
|
||||
from core.services.subscriptions.check_payment_loop import CheckPaidTimer
|
||||
from core.services.subscriptions.subscriptions_by_protocol import create_invoice, save_billing_code, get_profile_subscription
|
||||
from core.models.enum_models import PaymentType
|
||||
from core.services.payment_phase.payment_state import OverallPaymentCycle, PaymentData, TicketPaymentData
|
||||
from core.services.payment_phase.check_payment_loop import CheckPaidTimer
|
||||
from core.services.subscriptions.subscriptions_by_protocol import create_individual_invoice, save_billing_code, get_profile_subscription, start_billing_state, wipe_billing_cycle
|
||||
from core.models.enum_models import PaymentType, PaymentCategory
|
||||
from core.models.Result import Result, ResultError
|
||||
from core.errors.logger import logger
|
||||
|
||||
|
|
@ -13,24 +14,105 @@ from gui.v2.infrastructure.setup_observers import connection_observer
|
|||
# generic
|
||||
from typing import Callable, Optional
|
||||
|
||||
def gui_starts_invoice(profile_id: int, invoice_ui_caller: Callable, timer_resume_caller: Callable) -> str | None:
|
||||
"""Create a new invoice if we don't have one yet, or re-use the saved data. Returns any errors as strings for UI to display."""
|
||||
|
||||
subscription = get_profile_subscription(profile_id)
|
||||
def new_individual_billing(profile_id: int):
|
||||
"""Wipes pre-existing global payment cycle. And Calls upon core to setup a new individual profile cycle & data state."""
|
||||
logger.info("starting a new individual profile state. And wiping the old one")
|
||||
return start_billing_state(
|
||||
category=PaymentCategory.INDIVIDUAL,
|
||||
profile_id=profile_id,
|
||||
wipe_existing=True
|
||||
)
|
||||
|
||||
|
||||
def gui_starts_invoice(invoice_ui_caller: Callable, timer_resume_caller: Callable, profile_id: Optional[int] = None) -> str | None:
|
||||
|
||||
logger.info(f"gui_starts_invoice is being called with profile id {profile_id}")
|
||||
|
||||
# Is there currently a process?
|
||||
payment_cycle = OverallPaymentCycle()
|
||||
|
||||
# start a new individual one if not existing:
|
||||
if not payment_cycle:
|
||||
logger.info("There's no current payment cycle")
|
||||
if profile_id:
|
||||
return fetch_individual_invoice(invoice_ui_caller=invoice_ui_caller, timer_resume_caller=timer_resume_caller, profile_id=profile_id)
|
||||
else:
|
||||
return "Need a profile id for an individual profile."
|
||||
|
||||
billing_data = payment_cycle.instance
|
||||
category = payment_cycle.category
|
||||
|
||||
if not billing_data:
|
||||
msg = "There is a payment cycle, but no current billing data in it. So we're starting a new cycle.."
|
||||
logger.info(msg)
|
||||
result = new_individual_billing(profile_id=profile_id)
|
||||
if not result.valid:
|
||||
return result
|
||||
billing_data = result.data
|
||||
|
||||
# already conflicting one:
|
||||
elif profile_id and profile_id != payment_cycle.profile_id:
|
||||
logger.info("The new profile id request is different than the pre-existing running one. So we're wiping it and stopping the timer.")
|
||||
worked, message = stop_payment_timer()
|
||||
if not worked:
|
||||
return message
|
||||
|
||||
# start over:
|
||||
payment_cycle.wipe()
|
||||
new_data = new_individual_billing(profile_id)
|
||||
if not new_data.valid:
|
||||
return new_data # result object
|
||||
category = PaymentCategory.INDIVIDUAL
|
||||
# billing_data = new_cycle.data
|
||||
|
||||
#----------------------------
|
||||
# INVOICE DATA
|
||||
#----------------------------
|
||||
if category == PaymentCategory.INDIVIDUAL:
|
||||
if profile_id:
|
||||
return fetch_individual_invoice(invoice_ui_caller=invoice_ui_caller, timer_resume_caller=timer_resume_caller, profile_id=profile_id)
|
||||
else:
|
||||
return "Need a profile id for an individual profile."
|
||||
|
||||
elif category == PaymentCategory.TICKET:
|
||||
print("ready to start for tickets")
|
||||
|
||||
|
||||
|
||||
def fetch_individual_invoice(invoice_ui_caller: Callable, timer_resume_caller: Callable, profile_id: int) -> str | None:
|
||||
"""
|
||||
Purpose:
|
||||
Create a new invoice if we don't have one yet, or re-uses the saved data.
|
||||
And starts polling if it's not already.
|
||||
|
||||
Context:
|
||||
For individual profiles.
|
||||
|
||||
Returns:
|
||||
Returns any errors as strings for UI to display.
|
||||
"""
|
||||
# Fetch:
|
||||
take_action(
|
||||
action=create_invoice,
|
||||
action=create_individual_invoice,
|
||||
ui_caller=invoice_ui_caller,
|
||||
profile_id=profile_id,
|
||||
connection_observer=connection_observer
|
||||
)
|
||||
|
||||
#----------------------------
|
||||
# FILTER
|
||||
# FILTER ALREADY POLLING
|
||||
#----------------------------
|
||||
payment_cycle = OverallPaymentCycle()
|
||||
if payment_cycle.already_polling:
|
||||
logger.info("This cycle is already polling, so we don't need to start another poll")
|
||||
return None
|
||||
|
||||
#----------------------------
|
||||
# FILTER LACK OF DATA
|
||||
#----------------------------
|
||||
subscription = get_profile_subscription(profile_id)
|
||||
|
||||
if not subscription:
|
||||
logger.info(f"Started a New Invoice for Profile {profile_id}..")
|
||||
return None
|
||||
|
|
@ -50,6 +132,9 @@ def gui_starts_invoice(profile_id: int, invoice_ui_caller: Callable, timer_resum
|
|||
# Setup PyQt6 GUI as mainthread to bring back to:
|
||||
pyqt_threader = ThreadSafetyTool()
|
||||
|
||||
# update the global singleton:
|
||||
payment_cycle.already_polling = True
|
||||
|
||||
# setup the timer with the callback:
|
||||
# it doesn't return result here. It calls the view_callback function.
|
||||
check_paid_timer = CheckPaidTimer(
|
||||
|
|
@ -112,6 +197,10 @@ def save_and_begin_timer(
|
|||
def stop_payment_timer() -> tuple:
|
||||
"""Gets the single instance of the payment checker timer, and stops it."""
|
||||
try:
|
||||
# Wipe the cycle, so this is not the active poller (blocking others)
|
||||
wipe_billing_cycle()
|
||||
|
||||
# stop the timer:
|
||||
check_paid_timer = CheckPaidTimer.get_instance()
|
||||
if check_paid_timer:
|
||||
check_paid_timer.stop_polling()
|
||||
|
|
|
|||
34
gui/v2/actions/ticket_actions.py
Normal file
34
gui/v2/actions/ticket_actions.py
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# core
|
||||
from core.services.payment_phase.payment_state import OverallPaymentCycle, PaymentData, TicketPaymentData
|
||||
from core.models.enum_models import PaymentType
|
||||
from core.controllers.tickets.UseTicketController import get_unused_tickets
|
||||
from core.models.Result import Result, ResultError
|
||||
# from core.controllers.ProfileController import ProfileController
|
||||
|
||||
# gui
|
||||
from gui.v2.infrastructure.ThreadSafetyTool import ThreadSafetyTool
|
||||
from gui.v2.infrastructure.take_plumbing_action import take_action
|
||||
from gui.v2.infrastructure.setup_observers import connection_observer, ticket_observer
|
||||
|
||||
|
||||
|
||||
def get_ticket_data() -> Result:
|
||||
"""This is running in the background ALREADY. Get a list of unused tickets from core"""
|
||||
|
||||
ticket_results = get_unused_tickets()
|
||||
valid = ticket_results.get('valid')
|
||||
|
||||
if not valid:
|
||||
message = ticket_results.get('message')
|
||||
if not message:
|
||||
message = "Error gathering ticket data"
|
||||
Result(valid=False, error_type=ResultError.MISSING_FILE, message=message)
|
||||
|
||||
raw_tickets = ticket_results.get('data')
|
||||
print(f"raw_tickets is {raw_tickets}")
|
||||
if not raw_tickets:
|
||||
Result(valid=True, data=raw_tickets, message="No tickets available.")
|
||||
|
||||
formatted_data = [(f"Ticket #{t}", t) for t in raw_tickets]
|
||||
print(f"formatted_data is {formatted_data}")
|
||||
return Result(valid=True, data=formatted_data)
|
||||
|
|
@ -85,25 +85,25 @@ class Navigator(QObject):
|
|||
|
||||
def _preload_one(self, name):
|
||||
if name in self._instances:
|
||||
logger.debug(f"[preload] {name:30s} SKIP (already cached)")
|
||||
# logger.debug(f"[preload] {name:30s} SKIP (already cached)")
|
||||
return False
|
||||
if name not in PAGE_REGISTRY:
|
||||
logger.debug(f"[preload] {name:30s} SKIP (not in registry)")
|
||||
# logger.debug(f"[preload] {name:30s} SKIP (not in registry)")
|
||||
return False
|
||||
module_path, class_name = PAGE_REGISTRY[name]
|
||||
logger.debug(f"[preload] {name:30s} LOAD {module_path}.{class_name}")
|
||||
# logger.debug(f"[preload] {name:30s} LOAD {module_path}.{class_name}")
|
||||
t0 = time.perf_counter()
|
||||
try:
|
||||
page = self._instantiate(name)
|
||||
self.register_instance(name, page)
|
||||
dt_ms = (time.perf_counter() - t0) * 1000.0
|
||||
logger.debug(f"[preload] {name:30s} OK ({dt_ms:6.1f} ms, "
|
||||
f"cached={len(self._instances)}, stack_size={self.page_stack.count()})")
|
||||
# logger.debug(f"[preload] {name:30s} OK ({dt_ms:6.1f} ms, "
|
||||
# f"cached={len(self._instances)}, stack_size={self.page_stack.count()})")
|
||||
return True
|
||||
except Exception as e:
|
||||
dt_ms = (time.perf_counter() - t0) * 1000.0
|
||||
logger.debug(f"[preload] {name:30s} FAIL ({dt_ms:6.1f} ms) "
|
||||
f"{type(e).__name__}: {e}")
|
||||
# logger.debug(f"[preload] {name:30s} FAIL ({dt_ms:6.1f} ms) "
|
||||
# f"{type(e).__name__}: {e}")
|
||||
traceback.print_exc()
|
||||
from core.errors.logger import logger as core_logger
|
||||
core_logger.warning(f"Background preload failed for '{name}': "
|
||||
|
|
@ -126,22 +126,22 @@ class Navigator(QObject):
|
|||
self._preload_inflight = 0
|
||||
self._preload_summary_emitted = False
|
||||
self._preload_t_start = time.perf_counter()
|
||||
logger.debug(f"[preload] ============================================================")
|
||||
logger.debug(f"[preload] starting background preload: {self._preload_total} pages queued")
|
||||
logger.debug(f"[preload] order : {', '.join(self._preload_queue)}")
|
||||
logger.debug(f"[preload] skipped : {', '.join(sorted(PRELOAD_SKIP))} (unsafe init)")
|
||||
if fast_mode:
|
||||
logger.debug(f"[preload] fastmode: ON -> skipping regular-flow pages: "
|
||||
f"{', '.join(skipped_regular) if skipped_regular else '(none)'}")
|
||||
logger.debug(f"[preload] gap : 250 ms between loads, 700 ms initial delay")
|
||||
logger.debug(f"[preload] ============================================================")
|
||||
# logger.debug(f"[preload] ============================================================")
|
||||
# logger.debug(f"[preload] starting background preload: {self._preload_total} pages queued")
|
||||
# logger.debug(f"[preload] order : {', '.join(self._preload_queue)}")
|
||||
# logger.debug(f"[preload] skipped : {', '.join(sorted(PRELOAD_SKIP))} (unsafe init)")
|
||||
# if fast_mode:
|
||||
# logger.debug(f"[preload] fastmode: ON -> skipping regular-flow pages: "
|
||||
# f"{', '.join(skipped_regular) if skipped_regular else '(none)'}")
|
||||
# logger.debug(f"[preload] gap : 250 ms between loads, 700 ms initial delay")
|
||||
# logger.debug(f"[preload] ============================================================")
|
||||
self._preload_timer.start(700)
|
||||
|
||||
def _preload_next(self):
|
||||
while self._preload_queue and self._preload_queue[0] in self._instances:
|
||||
skipped = self._preload_queue.pop(0)
|
||||
self._preload_skipped_runtime += 1
|
||||
logger.debug(f"[preload] {skipped:30s} SKIP (user navigated here first)")
|
||||
# logger.debug(f"[preload] {skipped:30s} SKIP (user navigated here first)")
|
||||
if not self._preload_queue:
|
||||
self._maybe_emit_preload_summary()
|
||||
return
|
||||
|
|
@ -197,7 +197,7 @@ class Navigator(QObject):
|
|||
|
||||
def _on_preload_failed(self, name, error):
|
||||
self._preload_inflight -= 1
|
||||
logger.debug(f"[preload] {name:30s} FAIL (async prepare) {error}")
|
||||
# logger.debug(f"[preload] {name:30s} FAIL (async prepare) {error}")
|
||||
if name not in self._instances:
|
||||
ok = self._preload_one(name)
|
||||
if ok:
|
||||
|
|
|
|||
|
|
@ -61,10 +61,8 @@ REGULAR_FLOW_ONLY = {
|
|||
PRELOAD_ORDER = [
|
||||
"settings",
|
||||
"editor",
|
||||
"ticket_or_billing_choice",
|
||||
"plan_picker",
|
||||
"ticket_crypto_picker",
|
||||
"currency_selection",
|
||||
"policy_suggestion",
|
||||
"systemwide_prompt",
|
||||
"fast_mode_prompt",
|
||||
|
|
@ -86,3 +84,5 @@ PRELOAD_ORDER = [
|
|||
|
||||
# "duration_selection",
|
||||
# "payment_details",
|
||||
# "ticket_or_billing_choice",
|
||||
# "currency_selection",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
# core
|
||||
from core.services.subscriptions.subscriptions_by_protocol import paying_for_tickets, get_current_billing_data
|
||||
from core.controllers.profile_state.data_filters import ALL_CURRENCIES
|
||||
from core.models.enum_models import Currency
|
||||
from core.services.payment_phase.payment_state import PaymentData
|
||||
|
||||
# gui
|
||||
from gui.v2.ui.pages.Page import Page
|
||||
|
||||
import os
|
||||
|
|
@ -71,10 +75,27 @@ class CurrencySelectionPage(Page):
|
|||
profile_id = int(self.update_status.current_profile_id)
|
||||
|
||||
# get the singleton (this was created on a previous page)
|
||||
self.billing_data = PaymentData(instance_id=profile_id)
|
||||
print("doing the new method")
|
||||
data_result = get_current_billing_data()
|
||||
|
||||
if not data_result.valid:
|
||||
message = data_result.message
|
||||
if not message:
|
||||
message = "Error fetching data"
|
||||
self.show_a_popup(str(message))
|
||||
|
||||
# get the singleton (this was created on a previous page)
|
||||
billing_singleton = data_result.data
|
||||
|
||||
if not billing_singleton:
|
||||
message = "Error, no billing data"
|
||||
self.show_a_popup(str(message))
|
||||
|
||||
# prior direct method:
|
||||
# self.billing_data = PaymentData(instance_id=profile_id)
|
||||
|
||||
# set the value as an Enum to be used in a different page,
|
||||
self.billing_data.currency = self._get_selected_currency()
|
||||
billing_singleton.currency = self._get_selected_currency()
|
||||
|
||||
self.update_status.update_status('Loading payment details...')
|
||||
self.custom_window.navigator.navigate("payment_details")
|
||||
|
|
@ -91,4 +112,7 @@ class CurrencySelectionPage(Page):
|
|||
|
||||
|
||||
def reverse(self):
|
||||
self.custom_window.navigator.navigate("duration_selection")
|
||||
if paying_for_tickets():
|
||||
self.custom_window.navigator.navigate("plan_picker")
|
||||
else:
|
||||
self.custom_window.navigator.navigate("duration_selection")
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from core.controllers.profile_state.data_filters import get_plans_for_profile
|
|||
from core.models.Result import Result #, ResultError
|
||||
|
||||
# GUI
|
||||
from gui.v2.actions.payment_actions import new_individual_billing
|
||||
from gui.v2.ui.styles.payment_styles import DURATION_SELECTION_STYLE, NEXT_BUTTON_STYLE
|
||||
from gui.v2.ui.pages.Page import Page
|
||||
|
||||
|
|
@ -51,8 +52,18 @@ class DurationSelectionPage(Page):
|
|||
# get profile id:
|
||||
profile_id = int(self.update_status.current_profile_id)
|
||||
|
||||
# start entire payment cycle process:
|
||||
result = new_individual_billing(profile_id=profile_id)
|
||||
if not result.valid:
|
||||
self.show_a_popup(str(result.message))
|
||||
return
|
||||
|
||||
# setup singleton
|
||||
self.billing_data = PaymentData(instance_id=profile_id)
|
||||
self.billing_data = result.data
|
||||
# PaymentData(instance_id=profile_id)
|
||||
|
||||
if self.billing_data:
|
||||
print(f"we have billing data: {self.billing_data}")
|
||||
|
||||
# Thread SQL in background:
|
||||
take_action(
|
||||
|
|
@ -89,12 +100,14 @@ class DurationSelectionPage(Page):
|
|||
plan_id = self.dict_of_choices[current_choice]
|
||||
|
||||
# save to singleton across pages:
|
||||
print(f"saving plan_id to the billing data")
|
||||
self.billing_data.plan_id = plan_id
|
||||
|
||||
# go to next page:
|
||||
currency_page = self.custom_window.navigator.get_cached("currency_selection")
|
||||
if currency_page is not None:
|
||||
self.custom_window.navigator.navigate("currency_selection")
|
||||
print("going to the next page")
|
||||
# currency_page = self.custom_window.navigator.get_cached("currency_selection")
|
||||
# if currency_page is not None:
|
||||
self.custom_window.navigator.navigate("currency_selection")
|
||||
|
||||
def reverse(self):
|
||||
self.custom_window.navigator.navigate("id")
|
||||
|
|
|
|||
|
|
@ -1,17 +1,23 @@
|
|||
# core
|
||||
from core.services.subscriptions.subscriptions_by_protocol import paying_for_tickets
|
||||
from core.services.payment_phase.payment_state import PaymentData
|
||||
from core.models.pydantic_models.ServerReply import SubscriptionInvoice
|
||||
from core.models.enum_models import Currency
|
||||
from core.models.Result import Result
|
||||
|
||||
# GUI
|
||||
from gui.v2.actions.payment_actions import save_and_begin_timer, gui_starts_invoice, stop_payment_timer
|
||||
from gui.v2.actions.payment_actions import (
|
||||
save_and_begin_timer,
|
||||
gui_starts_invoice,
|
||||
stop_payment_timer,
|
||||
)
|
||||
from gui.v2.ui.pages.Page import Page, DataError
|
||||
from gui.v2.ui.popups.qrcode_dialog import QRCodeDialog
|
||||
from gui.v2.ui.styles.css.payment_styles import PAYMENT_STYLE_SHEET
|
||||
|
||||
# Bring background threads to the main thread UI:
|
||||
from gui.v2.infrastructure.ThreadSafetyTool import ThreadSafetyTool
|
||||
|
||||
mainthread = ThreadSafetyTool()
|
||||
|
||||
# generic
|
||||
|
|
@ -19,46 +25,69 @@ from typing import Optional
|
|||
import os
|
||||
from PyQt6 import QtCore, QtWidgets
|
||||
from PyQt6.QtWidgets import (
|
||||
QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
|
||||
QPushButton, QWidget, QApplication, QFrame, QSpacerItem, QSizePolicy, QStackedWidget, QMainWindow
|
||||
QVBoxLayout,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QPushButton,
|
||||
QWidget,
|
||||
QApplication,
|
||||
QFrame,
|
||||
QSpacerItem,
|
||||
QSizePolicy,
|
||||
QStackedWidget,
|
||||
QMainWindow,
|
||||
)
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtGui import QIcon
|
||||
|
||||
|
||||
class PaymentDetailsPage(Page):
|
||||
"""
|
||||
Display Payment Details, fetch an invoice, and launch a repeat timer to check payment status.
|
||||
|
||||
|
||||
From Page: Inherits display_error, show_a_popup
|
||||
From Navigator: gets the functions navigate, get_cached, page_stack object
|
||||
"""
|
||||
|
||||
def __init__(self, page_stack: QStackedWidget, main_window: Optional[QMainWindow] = None, parent: Optional[QWidget] = None):
|
||||
def __init__(
|
||||
self,
|
||||
page_stack: QStackedWidget,
|
||||
main_window: Optional[QMainWindow] = None,
|
||||
parent: Optional[QWidget] = None,
|
||||
):
|
||||
super().__init__("Payment Details", page_stack, main_window, parent)
|
||||
self.update_status = main_window
|
||||
|
||||
|
||||
self.fields = {}
|
||||
self.ticketing = False
|
||||
self.ticketing = paying_for_tickets()
|
||||
self.polling = False
|
||||
|
||||
self.setStyleSheet(PAYMENT_STYLE_SHEET)
|
||||
|
||||
|
||||
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
||||
|
||||
self.create_initial_elements()
|
||||
self.button_reverse.setVisible(True)
|
||||
# self.begin_invoice()
|
||||
|
||||
|
||||
def showEvent(self, event):
|
||||
# Call the parent class showEvent first
|
||||
super().showEvent(event)
|
||||
# runs every time the widget is displayed
|
||||
self.begin_invoice()
|
||||
|
||||
|
||||
def create_initial_elements(self):
|
||||
"""Setup Payment UI Buttons as part of a dict by field key."""
|
||||
main_layout = QVBoxLayout(self)
|
||||
|
||||
|
||||
# put the entire group (card + buttons) in the middle:
|
||||
main_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
main_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
main_layout.setSpacing(15)
|
||||
main_layout.setContentsMargins(10, 10, 10, 10)
|
||||
main_layout.setContentsMargins(10, 10, 10, 10)
|
||||
|
||||
# --- THE INFO CARD ---
|
||||
card = QFrame()
|
||||
|
|
@ -70,7 +99,7 @@ class PaymentDetailsPage(Page):
|
|||
def add_info_row(label_text, field_key=None, show_copy=False):
|
||||
row_layout = QHBoxLayout()
|
||||
row_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
|
||||
lbl = QLabel(label_text)
|
||||
lbl.setStyleSheet("font-size: 12px;")
|
||||
lbl.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
|
||||
|
|
@ -80,25 +109,29 @@ class PaymentDetailsPage(Page):
|
|||
edit = QLineEdit()
|
||||
edit.setReadOnly(True)
|
||||
edit.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.fields[field_key] = edit
|
||||
self.fields[field_key] = edit
|
||||
row_layout.addWidget(edit)
|
||||
|
||||
if show_copy:
|
||||
btn = QPushButton()
|
||||
btn.setFixedSize(28, 28)
|
||||
btn.setIcon(QIcon(os.path.join(self.btn_path, "paste_button.png")))
|
||||
btn.clicked.connect(lambda checked, key=field_key: self.copy_text(key))
|
||||
btn.clicked.connect(
|
||||
lambda checked, key=field_key: self.copy_text(key)
|
||||
)
|
||||
row_layout.addWidget(btn)
|
||||
|
||||
|
||||
card_layout.addLayout(row_layout)
|
||||
|
||||
add_info_row("Billing ID:", field_key="billing_code", show_copy=True)
|
||||
add_info_row("Subscription End-date", field_key="duration", show_copy=False)
|
||||
add_info_row("Currency:", field_key="currency", show_copy=False)
|
||||
add_info_row("Amount:", field_key="amount", show_copy=True)
|
||||
|
||||
|
||||
addr_sep = QLabel("Wallet Address")
|
||||
addr_sep.setStyleSheet("font-size: 11px; font-weight: bold; color: #89b4fa; margin-top: 2px;")
|
||||
addr_sep.setStyleSheet(
|
||||
"font-size: 11px; font-weight: bold; color: #89b4fa; margin-top: 2px;"
|
||||
)
|
||||
card_layout.addWidget(addr_sep)
|
||||
add_info_row("", field_key="address", show_copy=True)
|
||||
|
||||
|
|
@ -113,13 +146,13 @@ class PaymentDetailsPage(Page):
|
|||
self.qr_code_button.setMinimumHeight(40)
|
||||
self.qr_code_button.clicked.connect(self.show_qr_code)
|
||||
self.qr_code_button.setDisabled(True)
|
||||
|
||||
|
||||
self.stop_button = QPushButton(" Stop Checking")
|
||||
self.stop_button.setObjectName("StopButton")
|
||||
self.stop_button.setMinimumHeight(40)
|
||||
self.stop_button.clicked.connect(self.stop_or_start_check)
|
||||
self.stop_button.setDisabled(True)
|
||||
|
||||
|
||||
self.switch_to_background_button = QPushButton(" Check in Background")
|
||||
self.switch_to_background_button.setMinimumHeight(40)
|
||||
self.switch_to_background_button.clicked.connect(self.background_fetch_mode)
|
||||
|
|
@ -133,21 +166,31 @@ class PaymentDetailsPage(Page):
|
|||
self.button_reverse.clicked.connect(self.reverse)
|
||||
|
||||
def begin_invoice(self):
|
||||
""""Fetches an Invoice from GUI's actions modules"""
|
||||
self.update_status.update_status('Getting Invoice...')
|
||||
""" "Fetches an Invoice from GUI's actions modules"""
|
||||
self.update_status.update_status("Getting Invoice...")
|
||||
self.profile_id = int(self.update_status.current_profile_id)
|
||||
|
||||
print(f"profile_id is {self.profile_id}")
|
||||
|
||||
if not self.profile_id:
|
||||
self.display_error(whats_wrong=DataError.BLANK, whats_it_called="Profile ID", the_thing=profile_id)
|
||||
self.display_error(
|
||||
whats_wrong=DataError.BLANK,
|
||||
whats_it_called="Profile ID",
|
||||
the_thing=profile_id,
|
||||
)
|
||||
return
|
||||
|
||||
# this is not used here, but this is the right place to put it. Since we should have it by now:
|
||||
self.currency = self._get_currency()
|
||||
if not self.currency:
|
||||
return # error is already displayed in subfunction.
|
||||
return # error is already displayed in subfunction.
|
||||
|
||||
# launches background thread tasks:
|
||||
error_msg = gui_starts_invoice(profile_id=self.profile_id, invoice_ui_caller=self.receive_invoice, timer_resume_caller=self.timer_updates_gui)
|
||||
error_msg = gui_starts_invoice(
|
||||
invoice_ui_caller=self.receive_invoice,
|
||||
timer_resume_caller=self.timer_updates_gui,
|
||||
profile_id=self.profile_id,
|
||||
)
|
||||
|
||||
if error_msg:
|
||||
self.show_a_popup(error_msg)
|
||||
|
|
@ -157,7 +200,6 @@ class PaymentDetailsPage(Page):
|
|||
self.stop_button.setDisabled(False)
|
||||
self.switch_to_background_button.setDisabled(False)
|
||||
|
||||
|
||||
def stop_or_start_check(self):
|
||||
"""Toggle On/Off Polling"""
|
||||
# If it's on:
|
||||
|
|
@ -177,7 +219,6 @@ class PaymentDetailsPage(Page):
|
|||
else:
|
||||
self.begin_invoice()
|
||||
|
||||
|
||||
def background_fetch_mode(self):
|
||||
"""Continue checking if paid, but navigate back to the menu page"""
|
||||
if self.polling:
|
||||
|
|
@ -186,36 +227,42 @@ class PaymentDetailsPage(Page):
|
|||
message = "It was not polling to begin with."
|
||||
self.show_a_popup(message)
|
||||
|
||||
|
||||
def copy_text(self, key: str):
|
||||
"""Copy/Paste fields and update the bottom status"""
|
||||
try:
|
||||
# Use the key to determine if we use elided text or the full raw address
|
||||
text = self.full_address if key == "address" else self.fields[key].text()
|
||||
QApplication.clipboard().setText(text)
|
||||
|
||||
|
||||
# messages based on the key
|
||||
msg_map = {
|
||||
"billing_code": "Billing code copied!",
|
||||
"address": "Address copied!",
|
||||
"amount": "Pay amount copied!"
|
||||
"amount": "Pay amount copied!",
|
||||
}
|
||||
self.update_status.update_status(msg_map.get(key, "Copied!"))
|
||||
except Exception as e:
|
||||
self.show_a_popup(str(e))
|
||||
self.update_status.update_status('No content available for copying')
|
||||
|
||||
self.update_status.update_status("No content available for copying")
|
||||
|
||||
@mainthread
|
||||
def receive_invoice(self, invoice_result: Result):
|
||||
"""Callback for the background API thread to update the UI"""
|
||||
|
||||
if not invoice_result:
|
||||
self.display_error(whats_wrong=DataError.BLANK, whats_it_called="Backend Invoice Result", the_thing=invoice_result)
|
||||
self.display_error(
|
||||
whats_wrong=DataError.BLANK,
|
||||
whats_it_called="Backend Invoice Result",
|
||||
the_thing=invoice_result,
|
||||
)
|
||||
return
|
||||
|
||||
if not isinstance(invoice_result, Result):
|
||||
self.display_error(whats_wrong=DataError.FORMAT, whats_it_called="Backend Invoice Result", the_thing=invoice_result)
|
||||
self.display_error(
|
||||
whats_wrong=DataError.FORMAT,
|
||||
whats_it_called="Backend Invoice Result",
|
||||
the_thing=invoice_result,
|
||||
)
|
||||
return
|
||||
|
||||
if not invoice_result.valid:
|
||||
|
|
@ -227,29 +274,38 @@ class PaymentDetailsPage(Page):
|
|||
invoice = invoice_result.data
|
||||
|
||||
if not invoice:
|
||||
self.display_error(whats_wrong=DataError.BLANK, whats_it_called="invoice", the_thing=invoice)
|
||||
self.display_error(
|
||||
whats_wrong=DataError.BLANK,
|
||||
whats_it_called="invoice",
|
||||
the_thing=invoice,
|
||||
)
|
||||
return
|
||||
|
||||
if not isinstance(invoice, SubscriptionInvoice):
|
||||
self.display_error(whats_wrong=DataError.FORMAT, whats_it_called="invoice", the_thing=invoice)
|
||||
self.display_error(
|
||||
whats_wrong=DataError.FORMAT,
|
||||
whats_it_called="invoice",
|
||||
the_thing=invoice,
|
||||
)
|
||||
return
|
||||
|
||||
self.fill_in_invoice_data(invoice)
|
||||
self.update_status.update_status('Invoice generated. Awaiting payment...')
|
||||
self.update_status.update_status("Invoice generated. Awaiting payment...")
|
||||
|
||||
# kick off the timer and save the billing id,
|
||||
saved_and_started = save_and_begin_timer(
|
||||
ticketing=self.ticketing,
|
||||
ticketing=paying_for_tickets(),
|
||||
gui_callback=self.timer_updates_gui,
|
||||
billing_code=invoice.billing_code,
|
||||
profile_id=int(self.update_status.current_profile_id),
|
||||
expires_at=invoice.expires_at
|
||||
expires_at=invoice.expires_at,
|
||||
)
|
||||
if not saved_and_started:
|
||||
self.show_a_popup("The save and start checking if paid did not even begin. Please see the error logs/")
|
||||
self.show_a_popup(
|
||||
"The save and start checking if paid did not even begin. Please see the error logs/"
|
||||
)
|
||||
self.update_status.update_status("Could not begin")
|
||||
|
||||
|
||||
def fill_in_invoice_data(self, invoice: SubscriptionInvoice):
|
||||
"""Extract and populate invoice data into the labels"""
|
||||
|
||||
|
|
@ -264,23 +320,27 @@ class PaymentDetailsPage(Page):
|
|||
|
||||
if duration:
|
||||
self.fields["duration"].setText(duration)
|
||||
self.fields["duration"].setStyleSheet('font-size: 13px;')
|
||||
self.fields["duration"].setStyleSheet("font-size: 13px;")
|
||||
|
||||
if amount_text:
|
||||
self.fields["amount"].setText(amount_text)
|
||||
|
||||
|
||||
# Dynamic font sizing for amount
|
||||
font_size = 14
|
||||
if len(amount_text) > 9: font_size = 15
|
||||
if len(amount_text) > 13: font_size = 10
|
||||
self.fields["amount"].setStyleSheet(f"font-size: {font_size}px; font-weight: bold;")
|
||||
if len(amount_text) > 9:
|
||||
font_size = 15
|
||||
if len(amount_text) > 13:
|
||||
font_size = 10
|
||||
self.fields["amount"].setStyleSheet(
|
||||
f"font-size: {font_size}px; font-weight: bold;"
|
||||
)
|
||||
|
||||
if self.full_address:
|
||||
metrics = self.fields["address"].fontMetrics()
|
||||
elided_text = metrics.elidedText(
|
||||
self.full_address,
|
||||
Qt.TextElideMode.ElideMiddle,
|
||||
self.fields["address"].width()
|
||||
self.full_address,
|
||||
Qt.TextElideMode.ElideMiddle,
|
||||
self.fields["address"].width(),
|
||||
)
|
||||
self.fields["address"].setText(elided_text)
|
||||
|
||||
|
|
@ -294,7 +354,6 @@ class PaymentDetailsPage(Page):
|
|||
self.stop_button.setDisabled(False)
|
||||
self.switch_to_background_button.setDisabled(False)
|
||||
|
||||
|
||||
@mainthread
|
||||
def timer_updates_gui(self, paid: bool, stopped: bool, message: str):
|
||||
"""Callback for the background thread timer to repeatedly update the UI"""
|
||||
|
|
@ -308,10 +367,9 @@ class PaymentDetailsPage(Page):
|
|||
if stopped and not paid:
|
||||
self._display_it_stopped(message)
|
||||
|
||||
|
||||
def _finish_after_paid(self):
|
||||
"""Last step. Notify the user and potentially navigate away."""
|
||||
self.update_status.update_status('Payment received.')
|
||||
self.update_status.update_status("Payment received.")
|
||||
|
||||
# TICKETS
|
||||
if self.ticketing:
|
||||
|
|
@ -320,7 +378,6 @@ class PaymentDetailsPage(Page):
|
|||
if prep_page:
|
||||
prep_page.start_prep()
|
||||
|
||||
|
||||
def _display_it_stopped(self, message: str):
|
||||
"""Notify the user if it's NOT paid, but stopped"""
|
||||
if not message:
|
||||
|
|
@ -330,15 +387,18 @@ class PaymentDetailsPage(Page):
|
|||
self.update_status.update_status("Timer Stopped")
|
||||
self.show_a_popup(error_msg)
|
||||
|
||||
|
||||
def show_qr_code(self):
|
||||
"""Launch an external QR scanning popup screen"""
|
||||
if self.full_address and self.currency:
|
||||
qr_dialog = QRCodeDialog(self.full_address, self.fields["amount"].text(), self.currency.value, self)
|
||||
qr_dialog = QRCodeDialog(
|
||||
self.full_address,
|
||||
self.fields["amount"].text(),
|
||||
self.currency.value,
|
||||
self,
|
||||
)
|
||||
qr_dialog.exec()
|
||||
else:
|
||||
self.update_status.update_status('No address available for QR code')
|
||||
|
||||
self.update_status.update_status("No address available for QR code")
|
||||
|
||||
def reverse(self):
|
||||
"""Navigate away from the page, depending on which payment flow."""
|
||||
|
|
@ -348,24 +408,39 @@ class PaymentDetailsPage(Page):
|
|||
else:
|
||||
self.custom_window.navigator.navigate("menu")
|
||||
|
||||
|
||||
def _get_currency(self):
|
||||
"""Get the currency from prior cached PaymentData"""
|
||||
|
||||
if not self.profile_id or not isinstance(self.profile_id, int):
|
||||
self.display_error(whats_wrong=DataError.FORMAT, whats_it_called="profile_id", the_thing=self.profile_id)
|
||||
|
||||
self.display_error(
|
||||
whats_wrong=DataError.FORMAT,
|
||||
whats_it_called="profile_id",
|
||||
the_thing=self.profile_id,
|
||||
)
|
||||
|
||||
billing_data = PaymentData(instance_id=self.profile_id)
|
||||
|
||||
if not billing_data:
|
||||
self.display_error(whats_wrong=DataError.BLANK, whats_it_called="Profile's Billing Data", the_thing=billing_data)
|
||||
self.display_error(
|
||||
whats_wrong=DataError.BLANK,
|
||||
whats_it_called="Profile's Billing Data",
|
||||
the_thing=billing_data,
|
||||
)
|
||||
|
||||
currency = billing_data.currency
|
||||
|
||||
if not currency:
|
||||
self.display_error(whats_wrong=DataError.BLANK, whats_it_called="currency", the_thing=currency)
|
||||
self.display_error(
|
||||
whats_wrong=DataError.BLANK,
|
||||
whats_it_called="currency",
|
||||
the_thing=currency,
|
||||
)
|
||||
|
||||
if not isinstance(currency, Currency):
|
||||
self.display_error(whats_wrong=DataError.FORMAT, whats_it_called="currency", the_thing=currency)
|
||||
self.display_error(
|
||||
whats_wrong=DataError.FORMAT,
|
||||
whats_it_called="currency",
|
||||
the_thing=currency,
|
||||
)
|
||||
|
||||
return currency
|
||||
|
|
|
|||
|
|
@ -1,16 +1,29 @@
|
|||
from PyQt6.QtWidgets import QLabel, QListWidget, QListWidgetItem
|
||||
from PyQt6 import QtCore
|
||||
# core
|
||||
from core.services.subscriptions.subscriptions_by_protocol import start_billing_state
|
||||
from core.models.enum_models import PaymentCategory
|
||||
|
||||
# gui
|
||||
# from gui.v2.actions.payment_actions import start_billing_state
|
||||
from gui.v2.ui.pages.Page import Page
|
||||
from gui.v2.ui.styles.styles import TERMINAL_LIST_QSS
|
||||
from gui.v2.workers.ticketing_worker_thread import TicketingWorkerThread
|
||||
|
||||
# generic
|
||||
from PyQt6.QtWidgets import QLabel, QListWidget, QListWidgetItem
|
||||
from PyQt6 import QtCore
|
||||
|
||||
|
||||
class PlanPickerPage(Page):
|
||||
def __init__(self, page_stack, main_window=None, parent=None):
|
||||
super().__init__("PickPlan", page_stack, main_window, parent)
|
||||
self.update_status = main_window
|
||||
|
||||
# this page only, immediate pricing display:
|
||||
self.pricing_data = None
|
||||
|
||||
# cross page cache overall data:
|
||||
self.billing_data = None
|
||||
|
||||
self.worker = None
|
||||
|
||||
self.title.setText("Pick a Plan")
|
||||
|
|
@ -39,6 +52,19 @@ class PlanPickerPage(Page):
|
|||
self.status_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
def start_sync(self):
|
||||
# STATE
|
||||
result = start_billing_state(category=PaymentCategory.TICKET)
|
||||
if not result.valid:
|
||||
error_msg = result.message
|
||||
if error_msg:
|
||||
self.show_a_popup(str(error_msg))
|
||||
else:
|
||||
self.show_a_popup("Error with Starting Billing")
|
||||
|
||||
# cache data to save the choice to it:
|
||||
self.billing_data = result.data
|
||||
|
||||
# Begin Tickets:
|
||||
self.plan_list.clear()
|
||||
self.status_label.setText("Syncing prices...")
|
||||
self.update_status.update_status("Syncing ticket prices...")
|
||||
|
|
@ -88,10 +114,23 @@ class PlanPickerPage(Page):
|
|||
plan_key = item.data(QtCore.Qt.ItemDataRole.UserRole)
|
||||
if not plan_key:
|
||||
return
|
||||
self.custom_window.navigator.navigate("ticket_crypto_picker")
|
||||
crypto_page = self.custom_window.navigator.get_cached("ticket_crypto_picker")
|
||||
if crypto_page is not None:
|
||||
crypto_page.set_selected_plan(plan_key)
|
||||
|
||||
print(f"plan_key is {plan_key}")
|
||||
|
||||
# in temp cache save choice:
|
||||
self.billing_data.which_key = plan_key
|
||||
|
||||
# go to next page:
|
||||
currency_page = self.custom_window.navigator.get_cached("currency_selection")
|
||||
if currency_page is not None:
|
||||
self.custom_window.navigator.navigate("currency_selection")
|
||||
|
||||
|
||||
|
||||
# self.custom_window.navigator.navigate("ticket_crypto_picker")
|
||||
# crypto_page = self.custom_window.navigator.get_cached("ticket_crypto_picker")
|
||||
# if crypto_page is not None:
|
||||
# crypto_page.set_selected_plan(plan_key)
|
||||
|
||||
def on_error(self, msg):
|
||||
self.status_label.setText(f"Error: {msg}")
|
||||
|
|
|
|||
|
|
@ -1,13 +1,25 @@
|
|||
from PyQt6.QtWidgets import QLabel, QListWidget, QListWidgetItem, QPushButton
|
||||
from PyQt6 import QtCore
|
||||
|
||||
from core.controllers.ProfileController import ProfileController
|
||||
from core.controllers.tickets.UseTicketController import get_unused_tickets
|
||||
# core
|
||||
# from core.controllers.ProfileController import ProfileController
|
||||
# from core.controllers.tickets.UseTicketController import get_unused_tickets
|
||||
from core.models.Result import Result, ResultError
|
||||
|
||||
# gui
|
||||
from gui.v2.ui.styles.css.ticket_billing_styles import TICKET_LIST_STYLE_ONE, TICKET_BUTTON_A, USE_BILLING_BUTTON_STYLE
|
||||
from gui.v2.actions.locations import location_candidates
|
||||
from gui.v2.actions.menu_tasks import enable_profile
|
||||
from gui.v2.infrastructure.setup_observers import ticket_observer
|
||||
from gui.v2.ui.pages.Page import Page
|
||||
from gui.v2.infrastructure.take_plumbing_action import take_action
|
||||
from gui.v2.actions.ticket_actions import get_ticket_data
|
||||
|
||||
# Bring background threads to the main thread UI:
|
||||
from gui.v2.infrastructure.ThreadSafetyTool import ThreadSafetyTool
|
||||
mainthread = ThreadSafetyTool()
|
||||
|
||||
|
||||
from PyQt6.QtWidgets import QLabel, QListWidget, QListWidgetItem, QPushButton
|
||||
from PyQt6 import QtCore
|
||||
|
||||
|
||||
|
||||
class TicketOrBillingChoicePage(Page):
|
||||
|
|
@ -32,87 +44,103 @@ class TicketOrBillingChoicePage(Page):
|
|||
|
||||
self.ticket_list = QListWidget(self)
|
||||
self.ticket_list.setGeometry(60, 140, 380, 320)
|
||||
self.ticket_list.setStyleSheet("""
|
||||
QListWidget {
|
||||
background-color: #000000; color: #00ffff;
|
||||
border: 2px solid #00ffff; border-radius: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
QListWidget::item { padding: 10px; }
|
||||
QListWidget::item:hover { background-color: #003333; }
|
||||
QListWidget::item:selected { background-color: #006666; color: white; }
|
||||
""")
|
||||
self.ticket_list.setStyleSheet(TICKET_LIST_STYLE_ONE)
|
||||
|
||||
self.use_ticket_button = QPushButton("Use Selected Ticket", self)
|
||||
self.use_ticket_button.setGeometry(60, 470, 380, 40)
|
||||
self.use_ticket_button.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #00aaff; color: white; border: none;
|
||||
border-radius: 5px; font-size: 14px; font-weight: bold;
|
||||
}
|
||||
QPushButton:disabled { background-color: #555555; }
|
||||
""")
|
||||
self.use_ticket_button.setStyleSheet(TICKET_BUTTON_A)
|
||||
self.use_ticket_button.clicked.connect(self.on_use_ticket)
|
||||
|
||||
self.use_billing_button = QPushButton("Use a Billing Code Instead", self)
|
||||
self.use_billing_button.setGeometry(470, 200, 280, 60)
|
||||
self.use_billing_button.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #000000; color: #00ffff;
|
||||
border: 2px solid #00ffff; border-radius: 10px;
|
||||
font-size: 14px; font-weight: bold;
|
||||
}
|
||||
QPushButton:hover { background-color: #003333; }
|
||||
""")
|
||||
self.use_billing_button.setStyleSheet(USE_BILLING_BUTTON_STYLE)
|
||||
self.use_billing_button.clicked.connect(self.on_use_billing)
|
||||
|
||||
self.status_label = QLabel("", self)
|
||||
self.status_label.setGeometry(20, 520, 760, 20)
|
||||
self.status_label.setStyleSheet("color: #cccccc; font-size: 12px;")
|
||||
self.status_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||
self.get_tickets()
|
||||
|
||||
def populate(self):
|
||||
def get_tickets(self):
|
||||
"""Gets tickets in the background"""
|
||||
take_action(
|
||||
action=get_ticket_data,
|
||||
ui_caller=self.populate
|
||||
)
|
||||
|
||||
@mainthread
|
||||
def populate(self, result: Result):
|
||||
"""Callback to Populate tickets."""
|
||||
self.ticket_list.clear()
|
||||
try:
|
||||
unused = get_unused_tickets(ticket_observer)
|
||||
except Exception as e:
|
||||
self.status_label.setText(f"Error reading tickets: {e}")
|
||||
|
||||
message = result.message
|
||||
|
||||
if not result.valid:
|
||||
self.status_label.setText("Error reading tickets")
|
||||
if message:
|
||||
self.show_a_popup(message) # this is an error
|
||||
return
|
||||
if unused.get('valid'):
|
||||
for t in unused.get('data', []):
|
||||
item = QListWidgetItem(f"Ticket #{t}")
|
||||
item.setData(QtCore.Qt.ItemDataRole.UserRole, t)
|
||||
self.ticket_list.addItem(item)
|
||||
else:
|
||||
self.status_label.setText(unused.get('message', 'No tickets available.'))
|
||||
|
||||
data = result.data
|
||||
if not data:
|
||||
if message:
|
||||
self.status_label.setText(message) # this is valid state of none
|
||||
return
|
||||
|
||||
# POPULATE
|
||||
for display_text, ticket_id in result.data:
|
||||
item = QListWidgetItem(display_text)
|
||||
item.setData(QtCore.Qt.ItemDataRole.UserRole, ticket_id)
|
||||
self.ticket_list.addItem(item)
|
||||
|
||||
def on_use_ticket(self):
|
||||
# 1. Get the visual item the user clicked on
|
||||
item = self.ticket_list.currentItem()
|
||||
|
||||
if not item:
|
||||
self.status_label.setText("Pick a ticket from the list first.")
|
||||
return
|
||||
|
||||
# 2. This returns the actual ID (e.g., 1), NOT the string "Ticket #1"
|
||||
which_ticket = item.data(QtCore.Qt.ItemDataRole.UserRole)
|
||||
profile_id = self.update_status.current_profile_id
|
||||
try:
|
||||
profile = ProfileController.get(int(profile_id))
|
||||
except Exception:
|
||||
profile = None
|
||||
candidates = location_candidates(profile)
|
||||
if not candidates:
|
||||
self.status_label.setText("Could not determine profile location.")
|
||||
return
|
||||
profile_data = {
|
||||
'id': int(profile_id),
|
||||
'use_ticket': which_ticket,
|
||||
'ticket_location': candidates[0]
|
||||
}
|
||||
menu_page = self.custom_window.navigator.get_cached("menu")
|
||||
if menu_page:
|
||||
self.update_status.update_status(f"Using ticket #{which_ticket}...")
|
||||
self.profile_enable_worker = enable_profile(
|
||||
profile_data, menu_page.handle_task_result, allow_uninstalled_tor=True,
|
||||
)
|
||||
self.custom_window.navigator.navigate("menu")
|
||||
|
||||
print(f"which_ticket is {which_ticket}")
|
||||
profile_id = int(self.update_status.current_profile_id)
|
||||
print(f"profile_id is {profile_id}")
|
||||
|
||||
|
||||
|
||||
# def on_use_ticket(self):
|
||||
# item = self.ticket_list.currentItem()
|
||||
# if not item:
|
||||
# self.status_label.setText("Pick a ticket from the list first.")
|
||||
# return
|
||||
# which_ticket = item.data(QtCore.Qt.ItemDataRole.UserRole)
|
||||
|
||||
|
||||
|
||||
# profile_id = self.update_status.current_profile_id
|
||||
# try:
|
||||
# profile = ProfileController.get(int(profile_id))
|
||||
# except Exception:
|
||||
# profile = None
|
||||
# candidates = location_candidates(profile)
|
||||
# if not candidates:
|
||||
# self.status_label.setText("Could not determine profile location.")
|
||||
# return
|
||||
# profile_data = {
|
||||
# 'id': int(profile_id),
|
||||
# 'use_ticket': which_ticket,
|
||||
# 'ticket_location': candidates[0]
|
||||
# }
|
||||
# menu_page = self.custom_window.navigator.get_cached("menu")
|
||||
# if menu_page:
|
||||
# self.update_status.update_status(f"Using ticket #{which_ticket}...")
|
||||
# self.profile_enable_worker = enable_profile(
|
||||
# profile_data, menu_page.handle_task_result, allow_uninstalled_tor=True,
|
||||
# )
|
||||
# self.custom_window.navigator.navigate("menu")
|
||||
|
||||
def on_use_billing(self):
|
||||
self.custom_window.navigator.navigate("id")
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ class TicketPrepPage(Page):
|
|||
|
||||
def on_error(self, msg):
|
||||
self.terminal.append(f"EXCEPTION: {msg}")
|
||||
self.status_label.setText(f"An unkown error occured")
|
||||
self.status_label.setText(f"An unknown error occured")
|
||||
self.continue_button.setEnabled(True)
|
||||
|
||||
def on_continue(self):
|
||||
|
|
|
|||
30
gui/v2/ui/styles/css/ticket_billing_styles.py
Normal file
30
gui/v2/ui/styles/css/ticket_billing_styles.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
|
||||
|
||||
|
||||
TICKET_LIST_STYLE_ONE = """
|
||||
QListWidget {
|
||||
background-color: #000000; color: #00ffff;
|
||||
border: 2px solid #00ffff; border-radius: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
QListWidget::item { padding: 10px; }
|
||||
QListWidget::item:hover { background-color: #003333; }
|
||||
QListWidget::item:selected { background-color: #006666; color: white; }
|
||||
"""
|
||||
|
||||
TICKET_BUTTON_A = """
|
||||
QPushButton {
|
||||
background-color: #00aaff; color: white; border: none;
|
||||
border-radius: 5px; font-size: 14px; font-weight: bold;
|
||||
}
|
||||
QPushButton:disabled { background-color: #555555; }
|
||||
"""
|
||||
|
||||
USE_BILLING_BUTTON_STYLE = """
|
||||
QPushButton {
|
||||
background-color: #000000; color: #00ffff;
|
||||
border: 2px solid #00ffff; border-radius: 10px;
|
||||
font-size: 14px; font-weight: bold;
|
||||
}
|
||||
QPushButton:hover { background-color: #003333; }
|
||||
"""
|
||||
Loading…
Reference in a new issue