From e7288c9f7ec54e99f0b1cc3c923efc90982c4ba7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Zatorski?= Date: Fri, 4 Sep 2026 14:35:52 +0200 Subject: [PATCH] Okno potwierdzenia zwarte, ostrzezenie tylko tam gdzie cos przepada UKLAD Okno korzystalo z komponentu `Modal`, ktory jest zbudowany pod FORMULARZE: naglowek oddzielony kreska, przycisk zamkniecia w rogu, przewijana tresc i stopka oddzielona druga kreska. Przy imporcie kontaktow to sluszne, ale przy pytaniu na dwa zdania te same elementy rozdymaly okno: dwie linie podzialu i krzyzyk zajmowaly wiecej miejsca niz sama tresc. Okno potwierdzenia jest krotkie z natury, wiec dostaje uklad ciagly: ikona ostrzezenia jako okragla plakietka OBOK tytulu zamiast osobnego paska, przyciski tuz pod trescia. Krzyzyka nie ma, bo bylby trzecim sposobem powiedzenia "nie", obok przycisku "Anuluj", klawisza Escape i klikniecia w tlo. Naklatka, Escape i blokada przewijania tla zostaja takie same jak w `Modal`: powtarzamy zachowanie, nie uklad. OSTRZEZENIE PRZESTAJE BYC WSZEDZIE Trzy operacje mialy `destructive`, choc niczego nie niszcza: - zatrzymanie nagrywania: plik zostaje, nic nie przepada. Nieodwracalne jest wylacznie to, ze nagranej chwili nie da sie powtorzyc, - anulowanie zamowienia: zamowienie jest dokumentem finansowym i ZOSTAJE w dzienniku, co mowila zreszta sama tresc komunikatu, - przywrocenie grafiki domyslnej: plik da sie wgrac z powrotem, a "przywroc domyslna" to cofniecie, nie zniszczenie. Przy zatrzymaniu nagrywania tresc powtarzala przy tym ostrzezenie wlasnymi slowami, wiec to samo bylo napisane dwa razy. Ostrzezenie uzyte wszedzie przestaje cokolwiek znaczyc tam, gdzie strata jest prawdziwa. Co-Authored-By: Claude Opus 5 --- frontend/src/components/Confirm.tsx | 121 +++++++++++++----- frontend/src/pages/orders/OrderListPage.tsx | 3 +- .../src/pages/settings/BrandingSection.tsx | 3 +- frontend/src/pages/webinars/SpeakQueue.tsx | 16 +-- 4 files changed, 99 insertions(+), 44 deletions(-) diff --git a/frontend/src/components/Confirm.tsx b/frontend/src/components/Confirm.tsx index f732e46..b209ecb 100644 --- a/frontend/src/components/Confirm.tsx +++ b/frontend/src/components/Confirm.tsx @@ -1,6 +1,5 @@ -import { createContext, useCallback, useContext, useMemo, useRef, useState, type ReactNode } from 'react' +import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, type ReactNode } from 'react' import { Icon } from './Icon' -import { Modal } from './Modal' import { Button } from './ui' /** @@ -76,41 +75,95 @@ export function ConfirmProvider({ children }: { children: ReactNode }) { {children} - {pending && ( - settle(false)} - footer={ -
- - -
- } - > -
- {pending.destructive && ( -
- -

- Tej operacji nie da się cofnąć. -

+ {pending && } + + ) +} + +/** + * Samo okno pytania. + * + * NIE UZYWA `Modal`, I TO JEST SWIADOME + * ------------------------------------------------------------------------ + * `Modal` jest zbudowany pod FORMULARZE: ma naglowek oddzielony kreska, + * przycisk zamkniecia w rogu, przewijana tresc i stopke oddzielona druga + * kreska. Przy imporcie kontaktow albo edycji banera to sluszne, ale przy + * pytaniu na dwa zdania te same elementy rozdymaja okno: dwie linie + * podzialu i krzyzyk zajmuja wtedy wiecej miejsca niz sama tresc. + * + * Okno potwierdzenia jest krotkie z natury, wiec dostaje uklad ciagly, bez + * kresek, a wyjscie prowadzi przez przycisk "Anuluj". Krzyzyk bylby trzecim + * sposobem powiedzenia "nie", obok przycisku, Escape i klikniecia w tlo. + * + * Naklatka, Escape i blokada przewijania tla sa te same co w `Modal`: + * powtarzamy zachowanie, a nie uklad. + */ +function ConfirmDialog({ + options, + onSettle, +}: { + options: ConfirmOptions + onSettle: (value: boolean) => void +}) { + useEffect(() => { + function onKey(event: KeyboardEvent) { + if (event.key === 'Escape') onSettle(false) + } + document.addEventListener('keydown', onKey) + + const previous = document.body.style.overflow + document.body.style.overflow = 'hidden' + + return () => { + document.removeEventListener('keydown', onKey) + document.body.style.overflow = previous + } + }, [onSettle]) + + return ( +
{ + if (event.target === event.currentTarget) onSettle(false) + }} + > +
+
+ {options.destructive && ( + + + + )} +
+

+ {options.title} +

+ {options.message && ( +
+ {options.message}
)} - {pending.message && ( -
{pending.message}
- )}
- - )} - +
+ +
+ + +
+
+
) } diff --git a/frontend/src/pages/orders/OrderListPage.tsx b/frontend/src/pages/orders/OrderListPage.tsx index dcf8d9c..4ef1067 100644 --- a/frontend/src/pages/orders/OrderListPage.tsx +++ b/frontend/src/pages/orders/OrderListPage.tsx @@ -320,7 +320,8 @@ function OrderDetailModal({ orderId, onClose }: { orderId: number; onClose: () = message: 'Kursant nie dostanie dostępu do kursu. Zamówienie zostaje w dzienniku jako anulowane.', confirmLabel: 'Anuluj zamówienie', - destructive: true, + // bez `destructive`: zamowienie jest dokumentem finansowym + // i zostaje w dzienniku. To zmiana stanu, nie zniszczenie. }) if (ok) cancel.mutate() }} diff --git a/frontend/src/pages/settings/BrandingSection.tsx b/frontend/src/pages/settings/BrandingSection.tsx index c3982c5..4478a2e 100644 --- a/frontend/src/pages/settings/BrandingSection.tsx +++ b/frontend/src/pages/settings/BrandingSection.tsx @@ -152,7 +152,8 @@ export function BrandingSection() { title: 'Przywrócić grafikę domyślną?', message: 'Wgrany plik zostanie usunięty, a instancja wróci do wyglądu sprzed jego dodania.', confirmLabel: 'Przywróć domyślną', - destructive: true, + // bez `destructive`: grafike da sie wgrac z powrotem, + // a "przywroc domyslna" to cofniecie, nie zniszczenie }) if (ok) remove.mutate(kind) }} diff --git a/frontend/src/pages/webinars/SpeakQueue.tsx b/frontend/src/pages/webinars/SpeakQueue.tsx index 15f52e7..d899d26 100644 --- a/frontend/src/pages/webinars/SpeakQueue.tsx +++ b/frontend/src/pages/webinars/SpeakQueue.tsx @@ -102,17 +102,17 @@ export function SpeakQueue({ if (recordingBusy) return if (egressId) { + /* + * Bez `destructive`: plik zostaje i nic nie przepada. Nieodwracalne jest + * wylacznie to, ze nagranej chwili nie da sie powtorzyc, a to za malo na + * czerwony alarm. Ostrzezenie uzyte wszedzie traci znaczenie tam, gdzie + * strata jest prawdziwa. + */ const ok = await confirm({ title: 'Zatrzymać nagrywanie?', - message: ( - <> - Nagrywanie tego spotkania zostanie zakończone, a plik trafi do zakładki Pliki.{' '} - Tego fragmentu nie nagrasz ponownie: ponowne włączenie zacznie nowe - nagranie od bieżącej chwili. - - ), + message: + 'Plik trafi do zakładki Pliki. Ponowne włączenie zacznie nowe nagranie od bieżącej chwili.', confirmLabel: 'Zatrzymaj nagrywanie', - destructive: true, }) if (!ok) return }