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
@@ -17,6 +17,7 @@ import {
Spinner,
Textarea,
} from '../../components/ui'
import { useConfirm } from '../../components/Confirm'
import { useToasts } from '../../components/Toasts'
import { formatLocal } from '../../lib/dates'
import { ENROLLMENT_STATUS_LABEL, ENROLLMENT_STATUS_TONE } from './courseLabels'
@@ -51,6 +52,7 @@ export function StudentDetailPage() {
const contactId = Number(id)
const queryClient = useQueryClient()
const toasts = useToasts()
const confirm = useConfirm()
const [tab, setTab] = useState<Tab>('kursy')
const [enrolling, setEnrolling] = useState(false)
@@ -169,15 +171,18 @@ export function StudentDetailPage() {
<Button
variant="secondary"
icon="key"
onClick={() => {
if (
window.confirm(
`Wysłać ${student.email} link do ustawienia nowego hasła? ` +
'Nie poznasz jego hasła: kursant ustawi je sam.',
)
) {
resetPassword.mutate()
}
onClick={async () => {
const ok = await confirm({
title: 'Wysłać link do ustawienia nowego hasła?',
message: (
<>
Link trafi na adres <strong>{student.email}</strong>. Nie poznasz jego hasła:
kursant ustawi je sam.
</>
),
confirmLabel: 'Wyślij link',
})
if (ok) resetPassword.mutate()
}}
disabled={resetPassword.isPending}
>
@@ -187,15 +192,24 @@ export function StudentDetailPage() {
<Button
variant={student.accountEnabled ? 'danger' : 'primary'}
icon={student.accountEnabled ? 'user-off' : 'check'}
onClick={() => {
onClick={async () => {
const next = !student.accountEnabled
const question = next
? `Włączyć konto ${student.email}?`
: `Wyłączyć konto ${student.email}? Kursant straci możliwość zalogowania się, ` +
'ale jego zapisy, postęp i certyfikaty zostaną zachowane.'
if (window.confirm(question)) {
setEnabled.mutate(next)
}
const question = next ? (
<>
Konto <strong>{student.email}</strong> odzyska możliwość zalogowania się.
</>
) : (
<>
Konto <strong>{student.email}</strong> straci możliwość zalogowania się, ale jego
zapisy, postęp i certyfikaty zostaną zachowane.
</>
)
const ok = await confirm({
title: next ? 'Włączyć konto kursanta?' : 'Wyłączyć konto kursanta?',
message: question,
confirmLabel: next ? 'Włącz konto' : 'Wyłącz konto',
})
if (ok) setEnabled.mutate(next)
}}
disabled={setEnabled.isPending}
>