Potwierdzenia operacji jako okno panelu, nie okno przegladarki
Deploy PROD / deploy (push) Successful in 25s
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:
@@ -3,6 +3,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { client, errorMessage, fieldErrors } from '../../api/client'
|
||||
import type { Page, User, UserRole } from '../../api/types'
|
||||
import { useAuth } from '../../auth/AuthContext'
|
||||
import { useConfirm } from '../../components/Confirm'
|
||||
import { useToasts } from '../../components/Toasts'
|
||||
import { Badge, Button, Card, ErrorState, Field, IconButton, Input, PasswordInput, Select, Spinner } from '../../components/ui'
|
||||
import { formatLocal } from '../../lib/dates'
|
||||
@@ -22,6 +23,7 @@ const EMPTY: FormState = { email: '', fullName: '', role: 'MODERATOR', active: t
|
||||
|
||||
export function UserListPage() {
|
||||
const queryClient = useQueryClient()
|
||||
const confirm = useConfirm()
|
||||
const toasts = useToasts()
|
||||
const { user: currentUser } = useAuth()
|
||||
|
||||
@@ -273,8 +275,18 @@ export function UserListPage() {
|
||||
<IconButton
|
||||
icon="key"
|
||||
label="Resetuj hasło"
|
||||
onClick={() => {
|
||||
if (window.confirm(`Zresetować hasło konta ${user.email}?`)) reset.mutate(user)
|
||||
onClick={async () => {
|
||||
const ok = await confirm({
|
||||
title: 'Zresetować hasło?',
|
||||
message: (
|
||||
<>
|
||||
Konto <strong>{user.email}</strong> dostanie nowe hasło jednorazowe
|
||||
i będzie musiało je zmienić przy najbliższym logowaniu.
|
||||
</>
|
||||
),
|
||||
confirmLabel: 'Zresetuj hasło',
|
||||
})
|
||||
if (ok) reset.mutate(user)
|
||||
}}
|
||||
disabled={reset.isPending}
|
||||
/>
|
||||
@@ -282,8 +294,19 @@ export function UserListPage() {
|
||||
<IconButton
|
||||
icon="user-off"
|
||||
label="Dezaktywuj"
|
||||
onClick={() => {
|
||||
if (window.confirm(`Dezaktywować konto ${user.email}?`)) deactivate.mutate(user.id)
|
||||
onClick={async () => {
|
||||
const ok = await confirm({
|
||||
title: 'Dezaktywować konto?',
|
||||
message: (
|
||||
<>
|
||||
Konto <strong>{user.email}</strong> straci dostęp do panelu
|
||||
natychmiast, także w otwartych już sesjach. Można je później
|
||||
włączyć z powrotem.
|
||||
</>
|
||||
),
|
||||
confirmLabel: 'Dezaktywuj konto',
|
||||
})
|
||||
if (ok) deactivate.mutate(user.id)
|
||||
}}
|
||||
disabled={deactivate.isPending}
|
||||
className="text-alarm-600 hover:bg-alarm-50"
|
||||
@@ -351,10 +374,19 @@ export function UserListPage() {
|
||||
<Button
|
||||
variant="ghost"
|
||||
icon="key"
|
||||
onClick={() => {
|
||||
if (window.confirm(`Zresetować hasło konta ${user.email}?`)) {
|
||||
reset.mutate(user)
|
||||
}
|
||||
onClick={async () => {
|
||||
const ok = await confirm({
|
||||
title: 'Zresetować hasło?',
|
||||
message: (
|
||||
<>
|
||||
Konto <strong>{user.email}</strong> dostanie nowe hasło
|
||||
jednorazowe i będzie musiało je zmienić przy najbliższym
|
||||
logowaniu.
|
||||
</>
|
||||
),
|
||||
confirmLabel: 'Zresetuj hasło',
|
||||
})
|
||||
if (ok) reset.mutate(user)
|
||||
}}
|
||||
disabled={reset.isPending}
|
||||
title="Resetuj hasło"
|
||||
@@ -365,10 +397,19 @@ export function UserListPage() {
|
||||
<Button
|
||||
variant="ghost"
|
||||
icon="user-off"
|
||||
onClick={() => {
|
||||
if (window.confirm(`Dezaktywować konto ${user.email}?`)) {
|
||||
deactivate.mutate(user.id)
|
||||
}
|
||||
onClick={async () => {
|
||||
const ok = await confirm({
|
||||
title: 'Dezaktywować konto?',
|
||||
message: (
|
||||
<>
|
||||
Konto <strong>{user.email}</strong> straci dostęp do panelu
|
||||
natychmiast, także w otwartych już sesjach. Można je później
|
||||
włączyć z powrotem.
|
||||
</>
|
||||
),
|
||||
confirmLabel: 'Dezaktywuj konto',
|
||||
})
|
||||
if (ok) deactivate.mutate(user.id)
|
||||
}}
|
||||
disabled={deactivate.isPending}
|
||||
title="Dezaktywuj"
|
||||
|
||||
Reference in New Issue
Block a user