diff --git a/packages/ui/src/components/ConfigureSSO/ChangeProviderDialog.tsx b/packages/ui/src/components/ConfigureSSO/ChangeProviderDialog.tsx index 44623de0505..9a064a014c9 100644 --- a/packages/ui/src/components/ConfigureSSO/ChangeProviderDialog.tsx +++ b/packages/ui/src/components/ConfigureSSO/ChangeProviderDialog.tsx @@ -1,14 +1,18 @@ +import { useId, useState } from 'react'; + import type { LocalizationKey } from '@/customizables'; import { Button, Col, descriptors, Flex, Heading, localizationKeys, Text, useLocalizations } from '@/customizables'; import { Card } from '@/elements/Card'; -import { withCardStateProvider } from '@/elements/contexts'; +import { useCardState, withCardStateProvider } from '@/elements/contexts'; import { Modal } from '@/elements/Modal'; +import { Alert } from '@/ui/elements/Alert'; +import { handleError } from '@/utils/errorHandler'; type ChangeProviderDialogProps = { isOpen: boolean; onClose: () => void; - onConfirm: () => void; - isSubmitting?: boolean; + /** Performs the provider change. Rejecting keeps the dialog open with the error shown inline. */ + onConfirm: () => Promise; nextProviderLabel: LocalizationKey; currentProviderLabel: LocalizationKey; contentRef: React.RefObject; @@ -19,11 +23,52 @@ export const ChangeProviderDialog = (props: ChangeProviderDialogProps): JSX.Elem return null; } + return ; +}; + +const ChangeProviderDialogContent = withCardStateProvider((props: ChangeProviderDialogProps) => { + const { onClose, onConfirm, nextProviderLabel, currentProviderLabel, contentRef } = props; + const { t } = useLocalizations(); + const card = useCardState(); + + const [isSubmitting, setIsSubmitting] = useState(false); + const titleId = useId(); + + const nextProvider = t(nextProviderLabel); + const currentProvider = t(currentProviderLabel); + + const handleConfirm = async () => { + card.setError(undefined); + setIsSubmitting(true); + + try { + // On success the wizard advances and unmounts this dialog; on failure we + // surface the error inline and keep the dialog open so the user can retry. + await onConfirm(); + } catch (err) { + handleError(err as Error, [], card.setError); + setIsSubmitting(false); + } + }; + + const handleClose = () => { + if (isSubmitting) { + return; + } + onClose(); + }; + return ( { + if (event.key === 'Escape') { + handleClose(); + } + }} containerSx={t => ({ alignItems: 'center', position: 'absolute', @@ -34,64 +79,60 @@ export const ChangeProviderDialog = (props: ChangeProviderDialogProps): JSX.Elem backdropFilter: `blur(${t.sizes.$2})`, })} > - - - ); -}; - -const ChangeProviderDialogContent = withCardStateProvider((props: ChangeProviderDialogProps) => { - const { onClose, onConfirm, isSubmitting, nextProviderLabel, currentProviderLabel } = props; - const { t } = useLocalizations(); + ({ borderRadius: t.radii.$md })} + > + ({ textAlign: 'start', padding: t.sizes.$5 })}> + ({ gap: t.space.$4 })}> + ({ gap: t.space.$2 })}> + ({ fontSize: t.fontSizes.$md })} + /> + + - const nextProvider = t(nextProviderLabel); - const currentProvider = t(currentProviderLabel); + {card.error && ( + + )} - return ( - ({ borderRadius: t.radii.$md })} - > - ({ textAlign: 'start', padding: t.sizes.$5 })}> - ({ gap: t.space.$4 })}> - ({ gap: t.space.$2 })}> - ({ fontSize: t.fontSizes.$md })} - /> - + ({ gap: t.space.$3 })} + > +