-
{t('systemConfig.title')}
- {error ?
{error}
: null}
- {message ?
{message}
: null}
+
+
+
{t('systemConfig.title')}
+
{t('systemConfig.intro')}
+
+ {error ?
{error}
: null}
+ {message ?
{message}
: null}
- {t('systemConfig.localAuth')}
-
+
+ {t('systemConfig.localAuth')}
{t('systemConfig.lockoutHelp')}
+
+
{settings.isError ? {t('systemConfig.loadFailed')}
: null}
- {settings.data ? (
+ {settings.isPending ? {t('systemConfig.loading')}
: null}
+ {settings.data && draft ? (
<>
-
-
- {!settings.data.passwordLoginEnabled && settings.data.selfRegistrationEnabled ? (
- {t('systemConfig.registrationRequiresLogin')}
+
+ {([
+ ['passwordLoginEnabled', 'passwordLogin', 'passwordLoginHelp'],
+ ['selfRegistrationEnabled', 'selfRegistration', 'selfRegistrationHelp'],
+ ] as const).map(([field, title, help]) => (
+
+ ))}
+
+ {!draft.passwordLoginEnabled && draft.selfRegistrationEnabled ? (
+ {t('systemConfig.registrationRequiresLogin')}
) : null}
+
+ {settingsDirty ? {t('systemConfig.unsaved')} : null}
+
+
+
>
) : null}
+
{t('systemConfig.lockoutWarning')}{error ? {error} : null}>}
+ confirmText={t('systemConfig.confirmDisableLogin')} variant="destructive"
+ onConfirm={async () => { if (await saveSettings()) setConfirmLoginOff(false) }} />
- {t('systemConfig.initialRoleRules')}
+
+ {t('systemConfig.initialRoleRules')}
+ {t('systemConfig.ruleIntro')}
+
- {t('systemConfig.ruleHelp')}
-
+
+ setEmail(event.target.value)} required />
+
+
-
+
+
{rules.isError || roles.isError ?
{t('systemConfig.loadFailed')}
: null}
+
{t('systemConfig.existingRules')}
+ {rules.isPending ?
{t('systemConfig.loading')}
: null}
+ {rules.data?.length === 0 ?
{t('systemConfig.noRules')}
: null}
{(rules.data ?? []).map((rule) => (
-
-
{rule.providerCode} · {rule.email}
-
{t(`systemConfig.status.${rule.status}`)}
+
+
+
+
{rule.email}
+
{t('systemConfig.provider')}: {rule.providerCode}
+
+
{t(`systemConfig.status.${rule.status}`)}
+
{rule.status === 'CONSUMED' ? (
-
+
{t('systemConfig.grantedTo', { userId: rule.grantedUserId, subject: rule.matchedSubject })}
-
+
) : null}
{rule.status === 'ACTIVE' ? (
- <>
-
))}
+
{ if (!open) setRuleToDisable(null) }}
+ closeOnConfirm={false}
+ title={t('systemConfig.disableTitle')} description={<>{t('systemConfig.disableConfirm')}{error ? {error} : null}>}
+ confirmText={t('systemConfig.disable')} variant="destructive"
+ onConfirm={async () => {
+ if (ruleToDisable && await run(() => disableRule.mutateAsync({ id: ruleToDisable.id, version: ruleToDisable.version }))) {
+ setRuleToDisable(null)
+ }
+ }} />
)
}
diff --git a/web/src/shared/components/confirm-dialog.tsx b/web/src/shared/components/confirm-dialog.tsx
index 90e757d3..cde4454f 100644
--- a/web/src/shared/components/confirm-dialog.tsx
+++ b/web/src/shared/components/confirm-dialog.tsx
@@ -1,4 +1,4 @@
-import { ReactNode, useRef } from 'react'
+import { ReactNode, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import {
Dialog,
@@ -21,6 +21,7 @@ interface ConfirmDialogProps {
onConfirm: () => void | Promise
contentTestId?: string
confirmButtonTestId?: string
+ closeOnConfirm?: boolean
}
export function ConfirmDialog({
@@ -34,34 +35,43 @@ export function ConfirmDialog({
onConfirm,
contentTestId,
confirmButtonTestId,
+ closeOnConfirm = true,
}: ConfirmDialogProps) {
const { t } = useTranslation()
const openRef = useRef(open)
+ const pendingRef = useRef(false)
+ const [pending, setPending] = useState(false)
openRef.current = open
const resolvedConfirmText = confirmText ?? t('dialog.confirm')
const resolvedCancelText = cancelText ?? t('dialog.cancel')
const handleConfirm = async () => {
- await onConfirm()
- // Skip close if the caller already closed (e.g. publish success + navigate).
- if (openRef.current) {
- onOpenChange(false)
+ if (pendingRef.current) return
+ pendingRef.current = true
+ setPending(true)
+ try {
+ await onConfirm()
+ // Skip close if the caller already closed (e.g. publish success + navigate).
+ if (closeOnConfirm && openRef.current) onOpenChange(false)
+ } finally {
+ pendingRef.current = false
+ setPending(false)
}
}
return (
-