Potwierdzenia operacji jako okno panelu, nie okno przegladarki
Deploy PROD / deploy (push) Successful in 25s

Panel pytal o potwierdzenie natywnym `window.confirm`. Takie okno wyglada
inaczej w kazdej przegladarce i w kazdym systemie, wyskakuje przy gornej
krawedzi zamiast na srodku i pokazuje nad trescia adres domeny ("Komunikat
ze strony ..."). W panelu, ktory poza tym jest spojnym interfejsem, czyta sie
to jak blad strony, a nie jak pytanie aplikacji.

CLAUDE.md wymagalo juz, zeby kazde okno modalne uzywalo komponentu `Modal`,
i tlumaczylo przy otwartym mikrofonie, dlaczego nie `window.confirm`. Regula
obowiazywala w jednym miejscu, a w 42 innych nie.

HOOK, A NIE OKNO W KAZDYM MIEJSCU Z OSOBNA
Potwierdzen jest ponad czterdziesci. Wlasny stan i wlasny `Modal` do kazdego
oznaczalyby kilkanascie linii na jedno pytanie i te sama pewnosc, ze predzej
czy pozniej ktores bedzie wygladalo inaczej. `useConfirm` oddaje funkcje
o ksztalcie zblizonym do `confirm`, wiec zamiana jest jednolinijkowa. Wynik
przychodzi obietnica, bo okno musi sie najpierw wyrenderowac.

TRESC MOWI, CO SIE STANIE
Przy okazji poprawione komunikaty: zamiast "Czy na pewno?" pytania nazywaja
obiekt i skutek. Plik zniknie ze skladu instancji, odbiorca straci dostep do
API, razem z banerem przepadnie jego statystyka wraz z liczbami. Etykieta
przycisku nazywa czynnosc ("Usun nagranie"), a nie mowi "OK".

NIEODWRACALNE WYROZNIONE, ODWRACALNE NIE
`destructive` daje czerwony przycisk i ostrzezenie "Tej operacji nie da sie
cofnac". Odwolanie webinaru i dezaktywacja konta go NIE dostaja: sa
odwracalne, a straszenie tam, gdzie nic nie przepada, rozmydla sygnal tam,
gdzie strata jest prawdziwa. Zgodnie z reguła, ze usuniecie webinaru ma
podpowiadac odwolanie jako lagodniejsza alternatywe.

Sprawdzone `npx tsc -b --force` (pelne typowanie bez cache) i `npm run build`.
Przy okazji poprawione dwa em dash sprzed tej zmiany, w `ApiKeyListPage`
i `ProviderPicker`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-04 14:13:16 +02:00
parent b96cf8d07d
commit 1599c7b0c1
27 changed files with 723 additions and 245 deletions
+38 -23
View File
@@ -18,6 +18,7 @@ import { Icon } from '../../components/Icon'
import { PAGE_SIZE, Pagination } from '../../components/Pagination'
import { SortableHeader } from '../../components/SortableHeader'
import type { SortState } from '../../components/SortableHeader'
import { useConfirm } from '../../components/Confirm'
import { useToasts } from '../../components/Toasts'
import { formatLocal } from '../../lib/dates'
import { COURSE_STATUS_LABEL, COURSE_STATUS_TONE, formatPrice } from './courseLabels'
@@ -315,6 +316,8 @@ function RowActions({
onArchive: () => void
onCopy: () => void
}) {
const confirm = useConfirm()
return (
<>
<IconLink to={panelPath(`/kursy/${course.id}/edycja`)} icon="pencil" label="Edytuj kurs" />
@@ -323,10 +326,17 @@ function RowActions({
<ActionButton
icon="upload"
label="Publikuj kurs"
onClick={() => {
if (window.confirm(`Opublikować kurs „${course.title}”?`)) {
onPublish()
}
onClick={async () => {
const ok = await confirm({
title: 'Opublikować kurs?',
message: (
<>
Kurs <strong>{course.title}</strong> pojawi się w ofercie Akademii.
</>
),
confirmLabel: 'Opublikuj kurs',
})
if (ok) onPublish()
}}
/>
)}
@@ -335,15 +345,18 @@ function RowActions({
<ActionButton
icon="history"
label="Archiwizuj kurs"
onClick={() => {
if (
window.confirm(
`Zarchiwizować kurs „${course.title}”?\n\n` +
'Kurs zniknie z oferty, ale osoby już zapisane zachowają dostęp.',
)
) {
onArchive()
}
onClick={async () => {
const ok = await confirm({
title: 'Zarchiwizować kurs?',
message: (
<>
Kurs <strong>{course.title}</strong> zniknie z oferty, ale osoby już zapisane
zachowają dostęp.
</>
),
confirmLabel: 'Zarchiwizuj kurs',
})
if (ok) onArchive()
}}
/>
)}
@@ -351,16 +364,18 @@ function RowActions({
<ActionButton
icon="copy"
label="Kopiuj kurs"
onClick={() => {
if (
window.confirm(
`Skopiować kurs „${course.title}”?\n\n` +
'Kopia przejmie program, opis i ustawienia certyfikatu.\n' +
'Nie przejdą zapisy kursantów ani ich postęp.',
)
) {
onCopy()
}
onClick={async () => {
const ok = await confirm({
title: 'Skopiować kurs?',
message: (
<>
Kopia kursu <strong>{course.title}</strong> przejmie program, opis i ustawienia
certyfikatu. Nie przejdą zapisy kursantów ani ich postęp.
</>
),
confirmLabel: 'Skopiuj kurs',
})
if (ok) onCopy()
}}
/>
</>