diff --git a/gui/__main__.py b/gui/__main__.py index 62d6a5f..988113b 100755 --- a/gui/__main__.py +++ b/gui/__main__.py @@ -1370,6 +1370,7 @@ class Page(QWidget): class Worker(QObject): update_signal = pyqtSignal(str, bool, int, int, str) change_page = pyqtSignal(str, bool) + ticket_data_loss = pyqtSignal(str, str) def __init__(self, profile_data): self.profile_data = profile_data @@ -1380,6 +1381,7 @@ class Worker(QObject): 'enabled', lambda event: self.handle_profile_status(event.subject, True)) self.profile_type = None self._ticket_error_emitted = False + self._consumed_ticket = None def run(self): self.profile = ProfileController.get(int(self.profile_data['id'])) @@ -1404,6 +1406,13 @@ class Worker(QObject): ProfileController.attach_subscription( self.profile, subscription) else: + if self._consumed_ticket is not None: + self._ticket_error_emitted = True + self.ticket_data_loss.emit( + self._consumed_ticket, + str(self.profile_data.get('billing_code', '')), + ) + return self.change_page.emit('The billing code is invalid.', True) return if self.profile: @@ -1516,8 +1525,10 @@ class Worker(QObject): continue billing_code = outcome.get('billing_code') if outcome.get('valid') and billing_code: + self._consumed_ticket = str(which_ticket) return billing_code if billing_code: + self._consumed_ticket = str(which_ticket) return billing_code msg = outcome.get('message', 'failed') last_msg = msg @@ -2632,10 +2643,30 @@ class MenuPage(Page): self.worker = Worker(profile_data) self.worker.update_signal.connect(self.update_gui_main_thread) self.worker.change_page.connect(self.change_app_page) + self.worker.ticket_data_loss.connect(self.show_ticket_data_loss_popup) thread = threading.Thread(target=self.worker.run) thread.start() + def show_ticket_data_loss_popup(self, ticket_number, billing_code): + menu_page = self.page_stack.findChild(MenuPage) + if menu_page: + self.page_stack.setCurrentWidget(menu_page) + self.update_status.update_status('Critical: invalid billing code from ticket.') + self.popup = TicketDataLossPopup( + self.update_status, + ticket_number=ticket_number, + billing_code=billing_code, + ) + parent = self.update_status + try: + px = parent.x() + (parent.width() - self.popup.width()) // 2 + py = parent.y() + (parent.height() - self.popup.height()) // 2 + self.popup.move(max(px, 0), max(py, 0)) + except Exception: + pass + self.popup.show() + def DisplayInstallScreen(self, package_name): install_page = self.page_stack.findChild(InstallSystemPackage) install_page.configure(package_name=package_name, distro='debian') @@ -8942,6 +8973,137 @@ class ConfirmationPopup(QWidget): self.oldPos = event.globalPosition().toPoint() +class TicketDataLossPopup(QWidget): + finished = pyqtSignal() + + def __init__(self, parent=None, ticket_number="", billing_code=""): + super().__init__(parent) + self.parent_window = parent + self.ticket_number = str(ticket_number) + self.billing_code = str(billing_code) + self.initUI() + + def initUI(self): + self.setFixedSize(640, 330) + self.setWindowFlags(Qt.WindowType.FramelessWindowHint) + self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) + + outer = QVBoxLayout(self) + outer.setContentsMargins(0, 0, 0, 0) + + bg = QWidget(self) + bg.setStyleSheet(""" + background-color: white; + border-radius: 12px; + border: 2px solid #ff4d4d; + """) + outer.addWidget(bg) + + content = QVBoxLayout(bg) + content.setContentsMargins(24, 10, 24, 14) + content.setSpacing(8) + + top_row = QHBoxLayout() + top_row.setContentsMargins(0, 0, 0, 0) + + header = QLabel("Critical Error") + header.setFont(QFont("Arial", 18, QFont.Weight.Bold)) + header.setStyleSheet( + "color: #ff3333; border: none; background: transparent;") + header.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter) + top_row.addWidget(header) + + top_row.addStretch() + + close_button = QPushButton("✕") + close_button.setStyleSheet(""" + QPushButton { + background-color: transparent; + color: #888888; + font-size: 18px; + font-weight: bold; + border: none; + } + QPushButton:hover { color: #ff4d4d; } + """) + close_button.setFixedSize(28, 28) + close_button.clicked.connect(self.close) + top_row.addWidget(close_button) + + content.addLayout(top_row) + + ticket_html = ( + f"" + f"#{self.ticket_number}" + ) + code_html = ( + f"" + f"{self.billing_code}" + ) + + message_html = ( + "
" + f"

There was a serious error. " + f"You used up ticket {ticket_html}, but the subscription code you " + f"received {code_html} is invalid.

" + "

Please contact customer support " + "immediately with this data.

" + "

Do not hit Enable on this " + "profile again until this is resolved — every retry burns another " + "ticket.

" + "
" + ) + + message_label = QLabel(message_html) + message_label.setFont(QFont("Arial", 11)) + message_label.setTextFormat(Qt.TextFormat.RichText) + message_label.setWordWrap(True) + message_label.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop) + message_label.setStyleSheet("border: none; background: transparent;") + content.addWidget(message_label, 1) + + button_row = QHBoxLayout() + button_row.addStretch() + + action_button = QPushButton("Acknowledge") + action_button.setFixedSize(160, 40) + action_button.setFont(QFont("Arial", 11, QFont.Weight.Bold)) + action_button.setStyleSheet(""" + QPushButton { + background-color: #ff4d4d; + border: none; + color: white; + border-radius: 6px; + font-weight: bold; + } + QPushButton:hover { background-color: #ff3333; } + """) + action_button.clicked.connect(self._on_acknowledge) + button_row.addWidget(action_button) + + button_row.addStretch() + content.addLayout(button_row) + + self.setWindowModality(Qt.WindowModality.ApplicationModal) + + def _on_acknowledge(self): + self.finished.emit() + self.close() + + def mousePressEvent(self, event): + self.oldPos = event.globalPosition().toPoint() + + def mouseMoveEvent(self, event): + delta = event.globalPosition().toPoint() - self.oldPos + self.move(self.x() + delta.x(), self.y() + delta.y()) + self.oldPos = event.globalPosition().toPoint() + + class EndpointVerificationPopup(QWidget): finished = pyqtSignal(bool, str)