UNPKG

@ory/elements-react

Version:

Ory Elements React - a collection of React components for authentication UIs.

1 lines 3.43 MB
{"version":3,"sources":["../../../src/theme/default/components/card/index.tsx","../../../src/theme/default/assets/ory-badge-horizontal.svg","../../../src/theme/default/assets/ory-badge-vertical.svg","../../../src/theme/default/components/card/badge.tsx","../../../src/theme/default/components/card/content.tsx","../../../src/theme/default/components/card/footer.tsx","../../../src/components/card/two-step/state-select-method.tsx","../../../src/context/component.tsx","../../../src/context/defaultNodeSorter.ts","../../../src/context/flow-context.tsx","../../../src/context/form-state.ts","../../../src/components/card/two-step/utils.ts","../../../src/theme/default/utils/form.ts","../../../src/util/ui/index.ts","../../../src/context/config.tsx","../../../src/util/client.ts","../../../src/context/intl-context.tsx","../../../src/components/card/header.tsx","../../../src/components/form/form-provider.tsx","../../../src/components/form/form-helpers.ts","../../../src/components/form/form-resolver.ts","../../../src/util/i18n/index.ts","../../../src/util/nodes.ts","../../../src/util/i18n/generated/kratosMessages.ts","../../../src/util/utilFixSDKTypesHelper.ts","../../../src/components/card/card.tsx","../../../src/components/card/footer.tsx","../../../src/components/card/content.tsx","../../../src/components/card/card-two-step.tsx","../../../src/components/card/two-step/state-method-active.tsx","../../../src/components/form/form.tsx","../../../src/components/form/useOryFormSubmit.ts","../../../src/util/onSubmitLogin.ts","../../../src/util/sdk-helpers/error.ts","../../../src/util/sdk-helpers/utils.ts","../../../src/util/onSubmitRecovery.ts","../../../src/util/onSubmitRegistration.ts","../../../src/util/onSubmitSettings.ts","../../../src/util/onSubmitVerification.ts","../../../src/components/form/messages.tsx","../../../src/components/form/nodes/node.tsx","../../../src/components/form/nodes/input.tsx","../../../src/components/form/nodes/node-button.tsx","../../../src/components/form/nodes/renderer/button-renderer.tsx","../../../src/components/form/nodes/renderer/checkbox-renderer.tsx","../../../src/components/form/nodes/renderer/consent-checkbox-renderer.tsx","../../../src/components/form/nodes/renderer/image-renderer.tsx","../../../src/components/form/nodes/renderer/input-renderer.tsx","../../../src/components/form/nodes/hooks/useInputProps.tsx","../../../src/components/form/nodes/renderer/select-renderer.tsx","../../../src/components/form/nodes/renderer/sso-button-renderer.tsx","../../../src/components/form/nodes/renderer/text-renderer.tsx","../../../src/components/form/nodes/renderer/index.ts","../../../src/components/form/nodes/renderer/hidden-input-renderer.tsx","../../../src/components/card/two-step/state-provide-identifier.tsx","../../../src/components/form/social.tsx","../../../src/components/form/settings-section.tsx","../../../src/components/form/useResendCode.ts","../../../src/components/card/card-consent.tsx","../../../src/components/generic/divider.tsx","../../../src/components/generic/page-header.tsx","../../../src/components/settings/settings-card.tsx","../../../src/util/i18n/settingsCardMessages.ts","../../../src/util/showToast.tsx","../../../src/components/settings/oidc-settings.tsx","../../../src/components/settings/passkey-settings.tsx","../../../src/components/settings/recovery-codes-settings.tsx","../../../src/components/settings/totp-settings.tsx","../../../src/components/settings/webauthn-settings.tsx","../../../src/locales/af.json","../../../src/locales/ak.json","../../../src/locales/am.json","../../../src/locales/ar.json","../../../src/locales/as.json","../../../src/locales/az.json","../../../src/locales/be.json","../../../src/locales/bg.json","../../../src/locales/bm.json","../../../src/locales/bn.json","../../../src/locales/ca.json","../../../src/locales/cs.json","../../../src/locales/da.json","../../../src/locales/de.json","../../../src/locales/el.json","../../../src/locales/en.json","../../../src/locales/es.json","../../../src/locales/et.json","../../../src/locales/fa.json","../../../src/locales/fi.json","../../../src/locales/fr.json","../../../src/locales/gu.json","../../../src/locales/ha.json","../../../src/locales/he.json","../../../src/locales/hi.json","../../../src/locales/hr.json","../../../src/locales/hu.json","../../../src/locales/hy.json","../../../src/locales/id.json","../../../src/locales/ig.json","../../../src/locales/it.json","../../../src/locales/ja.json","../../../src/locales/ka.json","../../../src/locales/kk.json","../../../src/locales/km.json","../../../src/locales/kn.json","../../../src/locales/ko.json","../../../src/locales/ku.json","../../../src/locales/ky.json","../../../src/locales/lt.json","../../../src/locales/lv.json","../../../src/locales/mk.json","../../../src/locales/ml.json","../../../src/locales/mn.json","../../../src/locales/mr.json","../../../src/locales/ms.json","../../../src/locales/my.json","../../../src/locales/ne.json","../../../src/locales/nl.json","../../../src/locales/no.json","../../../src/locales/or.json","../../../src/locales/pa.json","../../../src/locales/pl.json","../../../src/locales/ps.json","../../../src/locales/pt.json","../../../src/locales/ro.json","../../../src/locales/ru.json","../../../src/locales/sd.json","../../../src/locales/si.json","../../../src/locales/sk.json","../../../src/locales/sl.json","../../../src/locales/so.json","../../../src/locales/sq.json","../../../src/locales/sr.json","../../../src/locales/su.json","../../../src/locales/sv.json","../../../src/locales/sw.json","../../../src/locales/ta.json","../../../src/locales/te.json","../../../src/locales/tg.json","../../../src/locales/th.json","../../../src/locales/tk.json","../../../src/locales/tl.json","../../../src/locales/tr.json","../../../src/locales/ug.json","../../../src/locales/uk.json","../../../src/locales/ur.json","../../../src/locales/uz.json","../../../src/locales/vi.json","../../../src/locales/xh.json","../../../src/locales/yo.json","../../../src/locales/zh.json","../../../src/locales/zu.json","../../../src/locales/index.ts","../../../src/context/provider.tsx","../../../src/components/card/two-step/list-methods.tsx","../../../src/theme/default/utils/logout.ts","../../../src/theme/default/utils/url.ts","../../../src/theme/default/components/card/header.tsx","../../../src/theme/default/utils/constructCardHeader.ts","../../../src/theme/default/components/card/current-identifier-button.tsx","../../../src/theme/default/utils/attributes.ts","../../../src/util/omitAttributes.ts","../../../src/theme/default/assets/icons/arrow-left.svg","../../../src/theme/default/components/card/logo.tsx","../../../src/theme/default/utils/cn.ts","../../../src/theme/default/components/form/index.tsx","../../../src/theme/default/components/form/sso.tsx","../../../src/theme/default/provider-logos/apple.svg","../../../src/theme/default/provider-logos/auth0.svg","../../../src/theme/default/provider-logos/discord.svg","../../../src/theme/default/provider-logos/facebook.svg","../../../src/theme/default/provider-logos/github.svg","../../../src/theme/default/provider-logos/gitlab.svg","../../../src/theme/default/provider-logos/google.svg","../../../src/theme/default/provider-logos/linkedin.svg","../../../src/theme/default/provider-logos/microsoft.svg","../../../src/theme/default/provider-logos/slack.svg","../../../src/theme/default/provider-logos/spotify.svg","../../../src/theme/default/provider-logos/yandex.svg","../../../src/theme/default/provider-logos/x.svg","../../../src/theme/default/provider-logos/index.ts","../../../src/theme/default/components/form/spinner.tsx","../../../src/theme/default/components/form/button.tsx","../../../src/theme/default/components/card/auth-method-list-item.tsx","../../../src/theme/default/assets/icons/alert-triangle.svg","../../../src/theme/default/assets/icons/code-asterix.svg","../../../src/theme/default/assets/icons/code.svg","../../../src/theme/default/assets/icons/passkey.svg","../../../src/theme/default/assets/icons/password.svg","../../../src/theme/default/assets/icons/totp.svg","../../../src/theme/default/assets/icons/webauthn.svg","../../../src/theme/default/components/card/list-item.tsx","../../../src/theme/default/components/form/checkbox.tsx","../../../src/theme/default/components/ui/checkbox-label.tsx","../../../src/theme/default/components/form/group-container.tsx","../../../src/util/childCounter.ts","../../../src/theme/default/components/form/horizontal-divider.tsx","../../../src/theme/default/components/form/image.tsx","../../../src/theme/default/components/form/input.tsx","../../../src/theme/default/assets/icons/eye-off.svg","../../../src/theme/default/assets/icons/eye.svg","../../../src/theme/default/components/form/label.tsx","../../../src/theme/default/components/form/select.tsx","../../../src/theme/default/components/form/link-button.tsx","../../../src/theme/default/components/form/pin-code-input.tsx","../../../src/theme/default/components/form/shadcn/otp-input.tsx","../../../src/theme/default/components/form/section.tsx","../../../src/theme/default/components/form/text.tsx","../../../src/theme/default/components/generic/page-header.tsx","../../../src/theme/default/components/ui/user-menu.tsx","../../../src/theme/default/assets/icons/logout.svg","../../../src/theme/default/assets/icons/settings.svg","../../../src/theme/default/utils/user.ts","../../../src/theme/default/components/ui/dropdown-menu.tsx","../../../src/theme/default/components/ui/user-avater.tsx","../../../src/theme/default/assets/icons/user.svg","../../../src/theme/default/components/settings/settings-oidc.tsx","../../../src/theme/default/assets/icons/trash.svg","../../../src/theme/default/components/settings/settings-passkey.tsx","../../../src/theme/default/assets/icons/download.svg","../../../src/theme/default/assets/icons/refresh.svg","../../../src/theme/default/components/settings/settings-recovery-codes.tsx","../../../src/theme/default/components/settings/settings-totp.tsx","../../../src/theme/default/assets/icons/qrcode.svg","../../../src/theme/default/components/settings/settings-webauthn.tsx","../../../src/theme/default/assets/icons/key.svg","../../../src/theme/default/components/card/auth-method-list-container.tsx","../../../src/theme/default/components/form/captcha.tsx","../../../src/theme/default/components/form/consent-scope-checkbox.tsx","../../../src/theme/default/assets/icons/message.svg","../../../src/theme/default/assets/icons/personal.svg","../../../src/theme/default/assets/icons/phone.svg","../../../src/theme/default/components/generic/toast.tsx","../../../src/theme/default/assets/icons/x.svg","../../../src/theme/default/components/default-components.tsx","../../../src/theme/default/flows/error.tsx","../../../src/theme/default/flows/login.tsx","../../../src/theme/default/flows/recovery.tsx","../../../src/theme/default/flows/registration.tsx","../../../src/theme/default/flows/settings.tsx","../../../src/theme/default/flows/verification.tsx","../../../src/theme/default/flows/consent.tsx","../../../src/theme/default/utils/oauth2.ts"],"sourcesContent":["// Copyright © 2024 Ory Corp\n// SPDX-License-Identifier: Apache-2.0\n\nimport { OryCardProps, useOryConfiguration } from \"@ory/elements-react\"\nimport { Badge } from \"./badge\"\nimport { DefaultCardContent } from \"./content\"\nimport { DefaultCardFooter } from \"./footer\"\nimport { DefaultCardHeader } from \"./header\"\nimport { DefaultCardLogo } from \"./logo\"\nimport { DefaultCurrentIdentifierButton } from \"./current-identifier-button\"\nimport { ComponentPropsWithoutRef } from \"react\"\nimport { cn } from \"../../utils/cn\"\n\n/**\n * The DefaultCard component is a styled container that serves as the main card layout for Ory Elements.\n *\n * @param props - The properties for the DefaultCard component.\n * @returns\n * @group Components\n * @category Default Components\n */\nexport function DefaultCard({\n children,\n className,\n ...rest\n}: OryCardProps & ComponentPropsWithoutRef<\"div\">) {\n const { project } = useOryConfiguration()\n\n return (\n <div className={cn(\"ory-elements\", className)} {...rest}>\n <div className=\"flex w-full flex-1 items-start justify-center font-sans-default sm:w-[480px] sm:max-w-[480px] sm:items-center\">\n <div\n className=\"relative grid w-full grid-cols-1 gap-8 border-b border-form-border-default bg-form-background-default px-8 py-12 sm:rounded-cards sm:border sm:px-12 sm:py-14\"\n data-testid=\"ory/card\"\n >\n {children}\n {!project.hide_ory_branding && <Badge />}\n </div>\n </div>\n </div>\n )\n}\n\nexport {\n DefaultCardContent,\n DefaultCardFooter,\n DefaultCardHeader,\n DefaultCardLogo,\n DefaultCurrentIdentifierButton,\n}\n","import * as React from \"react\";\nconst SvgOryBadgeHorizontal = props => <svg xmlns=\"http://www.w3.org/2000/svg\" width={props?.width ? props.width : props?.size ?? 20} height={props?.height ? props.height : props?.size ?? 20} fill=\"none\" {...props}><path fill=\"currentColor\" d=\"M18.007 8h-1.71l2.007-3.996L16.296 0h1.711l1.145 2.301L20.327 0H22z\" /><path fill=\"currentColor\" fillRule=\"evenodd\" d=\"M12.902 4.86a2.47 2.47 0 0 0 1.796-2.365v-.038C14.687 1.097 13.592 0 12.245 0H9.037v1.44l1.208 1.985H9.04V8h1.51V4.949h.633L13.04 8h1.775zm-.876-1.431h.223c.52 0 .943-.427.943-.953a.95.95 0 0 0-.943-.952h-1.39zM4 0a4 4 0 1 0-.001 7.999A4 4 0 0 0 4 0M1.524 4a2.476 2.476 0 1 0 4.952 0 2.476 2.476 0 0 0-4.952 0\" clipRule=\"evenodd\" /></svg>;\nexport default SvgOryBadgeHorizontal;","import * as React from \"react\";\nconst SvgOryBadgeVertical = props => <svg xmlns=\"http://www.w3.org/2000/svg\" width={props?.width ? props.width : props?.size ?? 20} height={props?.height ? props.height : props?.size ?? 20} fill=\"none\" {...props}><path fill=\"currentColor\" d=\"M8 3.993v1.71L4.004 3.697 0 5.704V3.993l2.301-1.145L0 1.673V0z\" /><path fill=\"currentColor\" fillRule=\"evenodd\" d=\"M4.86 9.099a2.47 2.47 0 0 0-2.365-1.797h-.038C1.097 7.313 0 8.408 0 9.755v3.208h1.44l1.985-1.208v1.204H8v-1.51H4.949v-.633L8 8.96V7.185zm-1.431.875v-.223a.95.95 0 0 0-.953-.943.95.95 0 0 0-.952.943v1.39zM0 18a4 4 0 1 0 8 0 4 4 0 0 0-8 0m4 2.476a2.476 2.476 0 1 0 0-4.952 2.476 2.476 0 0 0 0 4.952\" clipRule=\"evenodd\" /></svg>;\nexport default SvgOryBadgeVertical;","// Copyright © 2024 Ory Corp\n// SPDX-License-Identifier: Apache-2.0\n\nimport OryLogoHorizontal from \"../../assets/ory-badge-horizontal.svg\"\nimport OryLogoVertical from \"../../assets/ory-badge-vertical.svg\"\n\nexport function Badge() {\n return (\n <div\n data-testid=\"ory/card/badge\"\n className=\"absolute border border-ory-border-default bg-ory-background-default p-2 font-bold text-ory-foreground-default max-sm:bottom-0 max-sm:left-8 max-sm:translate-y-full max-sm:rounded-b-branding max-sm:py-[7px] sm:top-8 sm:right-0 sm:translate-x-full sm:rounded-r-branding sm:pl-[7px]\"\n >\n <OryLogoHorizontal width={22} height={8} className=\"sm:hidden\" />\n <OryLogoVertical width={8} height={22} className=\"max-sm:hidden\" />\n </div>\n )\n}\n","// Copyright © 2024 Ory Corp\n// SPDX-License-Identifier: Apache-2.0\n\nimport { OryCardContentProps } from \"@ory/elements-react\"\n\n/**\n * Simply renders the children passed to it.\n *\n * @param props - pass children to render instead of the default Ory Card components\n * @returns\n * @group Components\n * @category Default Components\n */\nexport function DefaultCardContent({ children }: OryCardContentProps) {\n return children\n}\n","// Copyright © 2024 Ory Corp\n// SPDX-License-Identifier: Apache-2.0\n\nimport { FlowType, LoginFlow } from \"@ory/client-fetch\"\nimport {\n ConsentFlow,\n FormState,\n Node,\n useOryConfiguration,\n useOryFlow,\n} from \"@ory/elements-react\"\nimport { useIntl } from \"react-intl\"\nimport { toAuthMethodPickerOptions } from \"../../../../components/card/two-step/state-select-method\"\nimport { findScreenSelectionButton } from \"../../../../util/nodes\"\nimport {\n findNode,\n nodesToAuthMethodGroups,\n useNodeGroupsWithVisibleNodes,\n} from \"../../../../util/ui\"\nimport {\n isUiNodeInput,\n UiNodeInput,\n} from \"../../../../util/utilFixSDKTypesHelper\"\nimport { useClientLogout } from \"../../utils/logout\"\nimport { initFlowUrl, restartFlowUrl } from \"../../utils/url\"\n\n/**\n * DefaultCardFooter renders the default footer for the card component based on the current flow type.\n *\n * @returns The default card footer component that renders the appropriate footer based on the current flow type.\n * @group Components\n * @category Default Components\n */\nexport function DefaultCardFooter() {\n const oryFlow = useOryFlow()\n switch (oryFlow.flowType) {\n case FlowType.Login:\n return <LoginCardFooter flow={oryFlow.flow} />\n case FlowType.Registration:\n return <RegistrationCardFooter />\n case FlowType.Recovery:\n return <RecoveryCardFooter />\n case FlowType.Verification:\n return <VerificationCardFooter />\n case FlowType.OAuth2Consent:\n return <ConsentCardFooter flow={oryFlow.flow} />\n default:\n return null\n }\n}\n\nfunction shouldShowLogoutButton(\n flow: LoginFlow,\n formState: FormState,\n authMethods: string[],\n) {\n // Always for refresh flows, as we know there is a session\n if (flow.refresh) {\n return true\n }\n\n // In aal2 flows we sometimes show the logout button\n if (flow.requested_aal === \"aal2\") {\n // Always on the \"method selector\" screen\n if (formState.current === \"select_method\") {\n return true\n }\n // On the \"method active\" screen, if it's a code method\n // If the method is any other than code, we want to show a \"Choose another method\" button\n // This is handled below.\n // TODO: refactor this, to not have this logic in two places\n if (formState.current === \"method_active\" && flow.active === \"code\") {\n return true\n }\n // If there are no other methods, we want to show the logout button\n // This is the case when the user only has one method (e.g. code or totp), set up\n // and the user is on the \"method active\" screen\n // In that case there is no \"select_method\" state, so going back to that screen wouldn't work\n if (formState.current === \"method_active\" && authMethods.length === 1) {\n return true\n }\n }\n return false\n}\n\ntype LoginCardFooterProps = {\n flow: LoginFlow\n}\n\nfunction LoginCardFooter({ flow }: LoginCardFooterProps) {\n const { dispatchFormState, formState } = useOryFlow()\n const config = useOryConfiguration()\n const intl = useIntl()\n\n const authMethods = nodesToAuthMethodGroups(flow.ui.nodes)\n\n let returnTo = config.project.default_redirect_url\n if (flow.return_to) {\n returnTo = flow.return_to\n }\n if (!returnTo) {\n returnTo = restartFlowUrl(\n flow,\n `${config.sdk.url}/self-service/${FlowType.Login}/browser`,\n )\n }\n\n if (shouldShowLogoutButton(flow, formState, authMethods)) {\n return <LogoutButton returnTo={returnTo} />\n }\n\n return (\n <>\n {formState.current === \"provide_identifier\" &&\n config.project.registration_enabled &&\n !config.project.hide_registration_link && (\n <span className=\"leading-normal font-normal text-interface-foreground-default-primary antialiased\">\n {intl.formatMessage({\n id: \"login.registration-label\",\n defaultMessage: \"Don't have an account?\",\n })}{\" \"}\n <a\n className=\"text-button-link-brand-brand underline transition-colors hover:text-button-link-brand-brand-hover\"\n href={initFlowUrl(config.sdk.url, \"registration\", flow)}\n data-testid={\"ory/screen/login/action/register\"}\n >\n {intl.formatMessage({\n id: \"login.registration-button\",\n defaultMessage: \"Sign up\",\n })}\n </a>\n </span>\n )}\n {authMethods.length > 1 && formState.current === \"method_active\" && (\n <span className=\"leading-normal font-normal text-interface-foreground-default-primary antialiased\">\n <button\n className=\"text-button-link-brand-brand underline transition-colors hover:text-button-link-brand-brand-hover\"\n onClick={() => {\n dispatchFormState({\n type: \"action_clear_active_method\",\n })\n }}\n data-testid={\"ory/screen/login/mfa/action/selectMethod\"}\n >\n {intl.formatMessage({\n id: \"login.2fa.method.go-back\",\n defaultMessage: \"Choose another method\",\n })}\n </button>\n </span>\n )}\n {authMethods.length === 1 &&\n authMethods[0] === \"code\" &&\n formState.current === \"method_active\" && (\n <span className=\"leading-normal font-normal text-interface-foreground-default-primary antialiased\">\n <a\n className=\"text-button-link-brand-brand underline transition-colors hover:text-button-link-brand-brand-hover\"\n href={returnTo}\n data-testid={\"ory/screen/login/action/cancel\"}\n >\n {intl.formatMessage({\n id: \"login.2fa.go-back.link\",\n defaultMessage: \"Go back\",\n })}\n </a>\n </span>\n )}\n </>\n )\n}\n\ntype LogoutButtonProps = {\n returnTo?: string\n}\n\nfunction LogoutButton({ returnTo }: LogoutButtonProps) {\n const config = useOryConfiguration()\n const intl = useIntl()\n const { logoutFlow: logout, didLoad: didLoadLogout } = useClientLogout(config)\n\n return (\n <span className=\"leading-normal font-normal text-interface-foreground-default-primary antialiased\">\n {intl.formatMessage({\n id: \"login.2fa.go-back\",\n defaultMessage: \"Something isn't working?\",\n })}{\" \"}\n <a\n className=\"text-button-link-brand-brand underline transition-colors hover:text-button-link-brand-brand-hover\"\n href={logout ? logout?.logout_url : returnTo}\n data-testid={\n // Only add the test-id when the logout link has loaded.\n didLoadLogout ? \"ory/screen/login/action/logout\" : undefined\n }\n >\n {!didLoadLogout || logout\n ? intl.formatMessage({\n id: \"login.logout-button\",\n defaultMessage: \"Logout\",\n })\n : intl.formatMessage({\n id: \"login.2fa.go-back.link\",\n defaultMessage: \"Go back\",\n })}\n </a>\n </span>\n )\n}\n\nfunction RegistrationCardFooter() {\n const intl = useIntl()\n const { flow, formState, dispatchFormState } = useOryFlow()\n const config = useOryConfiguration()\n const visibleGroups = useNodeGroupsWithVisibleNodes(flow.ui.nodes)\n const authMethodBlocks = toAuthMethodPickerOptions(visibleGroups)\n\n const screenSelectionNode = findScreenSelectionButton(flow.ui.nodes)\n switch (formState.current) {\n case \"method_active\":\n if (!screenSelectionNode || Object.entries(authMethodBlocks).length < 2) {\n return null\n }\n\n return (\n <span className=\"leading-normal font-normal text-interface-foreground-default-primary antialiased\">\n <button\n className=\"text-button-link-brand-brand underline transition-colors hover:text-button-link-brand-brand-hover\"\n onClick={() => {\n dispatchFormState({\n type: \"action_clear_active_method\",\n })\n }}\n data-testid={\"ory/screen/registration/action/selectMethod\"}\n type=\"button\"\n >\n {intl.formatMessage({\n id: \"card.footer.select-another-method\",\n defaultMessage: \"Select another method\",\n })}\n </button>\n </span>\n )\n case \"select_method\":\n default:\n return (\n <span className=\"leading-normal font-normal text-interface-foreground-default-primary antialiased\">\n {intl.formatMessage({\n id: \"registration.login-label\",\n defaultMessage: \"Already have an account?\",\n })}{\" \"}\n <a\n className=\"text-button-link-brand-brand underline transition-colors hover:text-button-link-brand-brand-hover\"\n href={initFlowUrl(config.sdk.url, \"login\", flow)}\n data-testid={\"ory/screen/registration/action/login\"}\n >\n {intl.formatMessage({\n id: \"registration.login-button\",\n defaultMessage: \"Sign in\",\n })}\n </a>\n </span>\n )\n }\n}\n\nfunction RecoveryCardFooter() {\n return null\n}\n\nfunction VerificationCardFooter() {\n return null\n}\n\n/**\n * Props for the ConsentCardFooter component.\n *\n * @hidden\n * @inline\n */\ntype ConsentCardFooterProps = {\n /** The consent flow to render the footer for. */\n flow: ConsentFlow\n}\n\nfunction ConsentCardFooter({ flow }: ConsentCardFooterProps) {\n const rememberNode = findNode(flow.ui.nodes, {\n group: \"oauth2_consent\",\n node_type: \"input\",\n name: \"remember\",\n }) as UiNodeInput\n\n return (\n <div className=\"flex flex-col gap-8\">\n <div>\n <p className=\"leading-normal font-medium text-interface-foreground-default-secondary\">\n Make sure you trust {flow.consent_request.client?.client_name}\n </p>\n <p className=\"leading-normal text-interface-foreground-default-secondary\">\n You may be sharing sensitive information with this site or\n application.\n </p>\n </div>\n {rememberNode && <Node.Checkbox node={rememberNode} />}\n <div className=\"grid grid-cols-1 gap-2 md:grid-cols-2\">\n {flow.ui.nodes\n .filter(\n (n): n is UiNodeInput =>\n n.attributes.node_type === \"input\" &&\n n.attributes.type === \"submit\" &&\n isUiNodeInput(n),\n )\n .map((n) => {\n return <Node.Button key={n.attributes.value} node={n} />\n })}\n </div>\n <p className=\"text-sm\">\n <span className=\"text-interface-foreground-default-tertiary\">\n Authorizing will redirect to{\" \"}\n {flow.consent_request.client?.client_name}\n </span>\n </p>\n </div>\n )\n}\n","// Copyright © 2024 Ory Corp\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n isUiNodeScriptAttributes,\n UiNode,\n UiNodeGroupEnum,\n UiText,\n} from \"@ory/client-fetch\"\nimport { useIntl } from \"react-intl\"\nimport { useComponents, useNodeSorter, useOryFlow } from \"../../../context\"\nimport { kratosMessages } from \"../../../util/i18n/generated/kratosMessages\"\nimport {\n GroupedNodes,\n hasSingleSignOnNodes,\n useFunctionalNodes,\n useNodeGroupsWithVisibleNodes,\n} from \"../../../util/ui\"\nimport { OryForm } from \"../../form/form\"\nimport { OryCardValidationMessages } from \"../../form/messages\"\nimport { Node } from \"../../form/nodes/node\"\nimport { OryFormSsoForm } from \"../../form/social\"\nimport { OryCardHeader } from \"../header\"\nimport { OryCard, OryCardContent, OryCardFooter } from \"./../\"\nimport { AuthMethodList } from \"./list-methods\"\nimport { handleAfterFormSubmit } from \"./utils\"\n\n/**\n * Converts the visible groups of nodes into a format suitable for the\n * AuthMethodOptions\n *\n * @param visibleGroups - The visible groups of nodes\n */\nexport function toAuthMethodPickerOptions(\n visibleGroups: GroupedNodes,\n): UiNodeGroupEnum[] {\n return Object.values(UiNodeGroupEnum)\n .filter((group) => visibleGroups[group]?.length)\n .filter(\n (group) =>\n !(\n [\n UiNodeGroupEnum.Oidc,\n UiNodeGroupEnum.Saml,\n UiNodeGroupEnum.Default,\n UiNodeGroupEnum.IdentifierFirst,\n UiNodeGroupEnum.Profile,\n UiNodeGroupEnum.Captcha,\n ] as UiNodeGroupEnum[]\n ).includes(group),\n )\n}\n\nexport function SelectMethodForm() {\n const { Form, Card } = useComponents()\n const { flow, flowType, dispatchFormState } = useOryFlow()\n const { ui } = flow\n\n const nodeSorter = useNodeSorter()\n const sortNodes = (a: UiNode, b: UiNode) => nodeSorter(a, b, { flowType })\n\n const visibleGroups = useNodeGroupsWithVisibleNodes(ui.nodes)\n const authMethodBlocks = toAuthMethodPickerOptions(visibleGroups)\n const authMethodAdditionalNodes = useFunctionalNodes(ui.nodes)\n // TODO(jonas): rework this (again). The above doesn't work to include the credential nodes and the Captcha nodes behave slightly differently.\n // This is a workaround to include the credential nodes in the auth method blocks.\n const hiddenNodes = ui.nodes.filter(\n (n) =>\n n.group !== UiNodeGroupEnum.Captcha &&\n ((n.attributes.node_type === \"input\" && n.attributes.type === \"hidden\") ||\n isUiNodeScriptAttributes(n.attributes)),\n )\n\n return (\n <OryCard>\n <OryCardHeader />\n <OryCardContent>\n <OryCardValidationMessages />\n <OryFormSsoForm />\n {Object.entries(authMethodBlocks).length > 0 ? (\n <OryForm onAfterSubmit={handleAfterFormSubmit(dispatchFormState)}>\n <Form.Group>\n <Card.Divider />\n <AuthMethodList\n options={authMethodBlocks}\n setSelectedGroup={(group) =>\n dispatchFormState({\n type: \"action_select_method\",\n method: group,\n })\n }\n />\n {authMethodAdditionalNodes.sort(sortNodes).map((node, k) => (\n <Node node={node} key={k} />\n ))}\n </Form.Group>\n {hiddenNodes.map((node, k) => (\n <Node node={node} key={k} />\n ))}\n </OryForm>\n ) : (\n !hasSingleSignOnNodes(ui.nodes) && <NoMethodsMessage />\n )}\n </OryCardContent>\n <OryCardFooter />\n </OryCard>\n )\n}\n\nfunction NoMethodsMessage() {\n const intl = useIntl()\n const { Message } = useComponents()\n\n // This is defined in Ory Kratos as well.\n const noMethods: UiText = {\n id: 5000002,\n text: intl.formatMessage(kratosMessages[5000002]),\n type: \"error\",\n }\n\n return (\n <div data-testid={`ory/form/methods/local`}>\n <Message.Root>\n <Message.Content key={noMethods.id} message={noMethods} />\n </Message.Root>\n </div>\n )\n}\n","// Copyright © 2024 Ory Corp\n// SPDX-License-Identifier: Apache-2.0\n\nimport { UiNode, UiNodeGroupEnum } from \"@ory/client-fetch\"\nimport { createContext, PropsWithChildren, useContext } from \"react\"\nimport { OryFlowComponents } from \"../components\"\nimport { defaultNodeSorter } from \"./defaultNodeSorter\"\n\ntype ComponentContextValue = {\n components: OryFlowComponents\n nodeSorter: (a: UiNode, b: UiNode, ctx: { flowType: string }) => number\n groupSorter: (a: UiNodeGroupEnum, b: UiNodeGroupEnum) => number\n}\n\nconst ComponentContext = createContext<ComponentContextValue>({\n components: null!, // fine because we throw an error if it's not provided\n nodeSorter: () => 0,\n groupSorter: () => 0,\n})\n\n/**\n * The `useComponents` hook provides access to the Ory Flow components provided in the `OryComponentProvider`.\n *\n * You can use this hook to access the components defined in the `components` prop of the `OryComponentProvider`.\n *\n * @returns the current component context value.\n * @group Hooks\n */\nexport function useComponents() {\n const ctx = useContext(ComponentContext)\n if (!ctx) {\n throw new Error(\"useComponents must be used within a ComponentProvider\")\n }\n return ctx.components\n}\n\n/**\n * The `useNodeSorter` hook provides a way to access the node sorting function\n *\n * The node sorting function is used to determine the order of nodes in a flow based on their attributes and context.\n *\n * To customize the sorting behavior, you can provide a custom `nodeSorter` function to the `OryComponentProvider`.\n *\n * @returns a function that sorts nodes based on the provided context.\n * @group Hooks\n */\nexport function useNodeSorter() {\n const ctx = useContext(ComponentContext)\n if (!ctx) {\n throw new Error(\"useNodeSorter must be used within a ComponentProvider\")\n }\n return ctx.nodeSorter\n}\n\nexport function useGroupSorter() {\n const ctx = useContext(ComponentContext)\n if (!ctx) {\n throw new Error(\"useGroupSorter must be used within a ComponentProvider\")\n }\n return ctx.groupSorter\n}\n\nconst defaultGroupOrder: UiNodeGroupEnum[] = [\n UiNodeGroupEnum.Default,\n UiNodeGroupEnum.Profile,\n UiNodeGroupEnum.Password,\n UiNodeGroupEnum.Oidc,\n UiNodeGroupEnum.Code,\n UiNodeGroupEnum.LookupSecret,\n UiNodeGroupEnum.Passkey,\n UiNodeGroupEnum.Webauthn,\n UiNodeGroupEnum.Totp,\n]\n\nfunction defaultGroupSorter(a: UiNodeGroupEnum, b: UiNodeGroupEnum): number {\n const aGroupWeight = defaultGroupOrder.indexOf(a) ?? 999\n const bGroupWeight = defaultGroupOrder.indexOf(b) ?? 999\n\n return aGroupWeight - bGroupWeight\n}\n\ntype ComponentProviderProps = {\n components: OryFlowComponents\n nodeSorter?: (a: UiNode, b: UiNode, ctx: { flowType: string }) => number\n groupSorter?: (a: UiNodeGroupEnum, b: UiNodeGroupEnum) => number\n}\n\nexport function OryComponentProvider({\n children,\n components,\n nodeSorter = defaultNodeSorter,\n groupSorter = defaultGroupSorter,\n}: PropsWithChildren<ComponentProviderProps>) {\n return (\n <ComponentContext.Provider\n value={{\n components,\n nodeSorter,\n groupSorter,\n }}\n >\n {children}\n </ComponentContext.Provider>\n )\n}\n","// Copyright © 2026 Ory Corp\n// SPDX-License-Identifier: Apache-2.0\n\nimport { isUiNodeInputAttributes, UiNode } from \"@ory/client-fetch\"\n\nconst defaultNodeOrder = [\n \"oidc\",\n \"saml\",\n \"identifier_first\",\n \"default\",\n \"profile\",\n \"password\",\n \"captcha\",\n \"passkey\",\n \"code\",\n \"webauthn\",\n]\n\nconst Slot = {\n Inputs: 0,\n Checkboxes: 1,\n Captcha: 2,\n Buttons: 3,\n}\n\nfunction isUiNodeButton(node: UiNode) {\n return (\n isUiNodeInputAttributes(node.attributes) &&\n (node.attributes.type === \"submit\" || node.attributes.type === \"button\")\n )\n}\n\n// makeUiNodeComparator creates a comparator function for UiNodes based on the provided group order.\n// It sorts the nodes first by slot (inputs, checkboxes, captchas, submits), then by group order, and finally by type within the same slot.\nfunction makeUiNodeComparator({ groupOrder = defaultNodeOrder } = {}) {\n const groupRank = new Map(groupOrder.map((g, i) => [g, i]))\n const unknownGroupRank = groupOrder.length\n\n // Slot rank: 0 inputs, 1 checkboxes, 2 captchas, 3 submit/buttons\n const slotRank = (node: UiNode) => {\n if (isUiNodeInputAttributes(node.attributes) === false) {\n return Slot.Inputs // non-inputs go to default slot\n }\n const { type } = node.attributes\n\n // Keep webauthn inputs next to the webauthn button by treating them as buttons\n if (node.group === \"webauthn\" && type !== \"submit\" && type !== \"button\") {\n return Slot.Buttons\n }\n\n if (type === \"checkbox\") {\n return Slot.Checkboxes\n }\n\n // Captcha slot is based on group\n if (node.group === \"captcha\") {\n return Slot.Captcha\n }\n\n if (type === \"submit\" || type === \"button\") {\n return Slot.Buttons\n }\n\n // Default: inputs slot\n return Slot.Inputs\n }\n\n return (a: UiNode, b: UiNode) => {\n const sa = slotRank(a)\n const sb = slotRank(b)\n if (sa !== sb) {\n return sa - sb\n }\n\n const ga = groupRank.get(a.group) ?? unknownGroupRank\n const gb = groupRank.get(b.group) ?? unknownGroupRank\n if (ga !== gb) {\n return ga - gb\n }\n\n if (a.group === \"webauthn\" && b.group === \"webauthn\") {\n const aIsButton = isUiNodeButton(a)\n const bIsButton = isUiNodeButton(b)\n if (aIsButton !== bIsButton) {\n return aIsButton ? 1 : -1\n }\n }\n\n return 0 // stability handled by wrapper\n }\n}\n\nexport const defaultNodeSorter = makeUiNodeComparator({\n groupOrder: defaultNodeOrder,\n})\n","// Copyright © 2024 Ory Corp\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n Dispatch,\n PropsWithChildren,\n createContext,\n useContext,\n useState,\n} from \"react\"\nimport { OryFlowContainer } from \"../util/flowContainer\"\nimport {\n OryErrorHandler,\n OrySuccessHandler,\n OryValidationErrorHandler,\n} from \"../util/events\"\nimport { OryTransientPayload } from \"../util/transientPayload\"\nimport { FormState, FormStateAction, useFormStateReducer } from \"./form-state\"\n\n/**\n * Returns an object that contains the current flow and the flow type, as well as the configuration.\n *\n * @returns The current flow container\n * @group Hooks\n */\nexport function useOryFlow() {\n const ctx = useContext(OryFlowContext)\n if (!ctx) {\n throw new Error(\"useOryFlow must be used within a OryFlowProvider\")\n }\n\n return ctx\n}\n\n/**\n * Function to set the flow container.\n * @interface\n */\nexport type FlowContainerSetter = Dispatch<OryFlowContainer>\n\n/**\n * The return value of the OryFlowContext.\n */\nexport type FlowContextValue = OryFlowContainer & {\n /**\n * Function to set the flow container.\n */\n setFlowContainer: FlowContainerSetter\n\n /**\n * The current form state.\n * @see FormState\n */\n formState: FormState\n\n /**\n * Dispatch function to update the form state.\n */\n dispatchFormState: Dispatch<FormStateAction>\n\n /**\n * Optional callback invoked on successful flow completion.\n */\n onSuccess?: OrySuccessHandler\n\n /**\n * Optional callback invoked when the flow returns validation errors.\n */\n onValidationError?: OryValidationErrorHandler\n\n /**\n * Optional callback invoked when a flow error occurs.\n */\n onError?: OryErrorHandler\n\n /**\n * Optional transient payload to include in flow submissions.\n */\n transientPayload?: OryTransientPayload\n}\n\n// This is fine, because we don't export the context itself and guard from it being null in useOryFlow\nconst OryFlowContext = createContext<FlowContextValue>(null!)\n\n/**\n * Props type for the OryFlowProvider component.\n *\n * @hidden\n * @inline\n */\nexport type OryFlowProviderProps = PropsWithChildren<\n OryFlowContainer & {\n onSuccess?: OrySuccessHandler\n onValidationError?: OryValidationErrorHandler\n onError?: OryErrorHandler\n transientPayload?: OryTransientPayload\n }\n>\n\n/**\n *\n * @param props - The properties for the OryFlowProvider component.\n * @returns\n */\nexport function OryFlowProvider({\n children,\n onSuccess,\n onValidationError,\n onError,\n transientPayload,\n ...container\n}: OryFlowProviderProps) {\n const [flowContainer, setFlowContainer] = useState(container)\n const [formState, dispatchFormState] = useFormStateReducer(container)\n\n return (\n <OryFlowContext.Provider\n value={\n {\n ...flowContainer,\n setFlowContainer: (flowContainer) => {\n setFlowContainer(flowContainer)\n dispatchFormState({\n type: \"action_flow_update\",\n flow: flowContainer,\n })\n },\n formState,\n dispatchFormState,\n onSuccess,\n onValidationError,\n onError,\n transientPayload,\n } as FlowContextValue\n }\n >\n {children}\n </OryFlowContext.Provider>\n )\n}\n","// Copyright © 2024 Ory Corp\n// SPDX-License-Identifier: Apache-2.0\n\nimport { FlowType, UiNode, UiNodeGroupEnum } from \"@ory/client-fetch\"\nimport { useReducer, useState } from \"react\"\nimport { isChoosingMethod } from \"../components/card/two-step/utils\"\nimport { OryFlowContainer } from \"../util\"\nimport { nodesToAuthMethodGroups } from \"../util/ui\"\n\n/**\n * Represents the state of the form when selecting an authentication method.\n * This type is used when the user is in the process of selecting an authentication method\n * (e.g., password, passkey, etc.) during the login or registration flow.\n * @inline\n * @hidden\n */\nexport type FormStateSelectMethod = { current: \"select_method\" }\n/**\n * Represents the state of the form when providing an identifier.\n * This type is used when the user is required to provide an identifier (e.g., email or username)\n * before proceeding with the authentication flow.\n * @inline\n * @hidden\n */\nexport type FormStateProvideIdentifier = { current: \"provide_identifier\" }\n/**\n * Represents the state of the form when an authentication method is active.\n * This type is used when the user is interacting with a specific authentication method\n * (e.g., entering a password or entering a code received via email).\n *\n * The `method` field indicates which authentication method is currently active.\n * @inline\n * @hidden\n */\nexport type FormStateMethodActive = {\n current: \"method_active\"\n method: UiNodeGroupEnum\n}\n\ntype FlowFormState =\n | FormStateSelectMethod\n | FormStateProvideIdentifier\n | FormStateMethodActive\n | { current: \"success_screen\" }\n | { current: \"settings\" }\n\ntype CommonFormStateProperties = {\n isSubmitting: boolean\n isReady: boolean\n}\n\n/**\n * Represents the state of the form based on the flow type and active method.\n * This type is used to determine which part of the form should be displayed.\n *\n * It can be one of the following:\n * - `select_method`: The user is selecting an authentication method.\n * - `provide_identifier`: The user is providing an identifier (e.g., email or username).\n * - `method_active`: An authentication method is active, and the user is interacting with it.\n * - `success_screen`: The flow has successfully completed (only used in the verification flow).\n * - `settings`: The user is in the settings flow.\n *\n * In addition, it includes a common properties:\n * - `isSubmitting`: A boolean indicating whether the form is currently being submitted.\n * - `isReady`: A boolean indicating whether the form is ready.\n */\nexport type FormState = FlowFormState & CommonFormStateProperties\n\n/**\n * Represents the actions that can be dispatched to update the form state.\n * These actions are used to change the current state of the form based on user interactions or flow updates.\n */\nexport type FormStateAction =\n | {\n /**\n * Action to update the flow state.\n * This action is dispatched when the flow is updated, and it will parse the new flow\n * to determine the current form state.\n */\n type: \"action_flow_update\"\n /**\n * The updated flow container that contains the new flow data.\n */\n flow: OryFlowContainer\n }\n | {\n /**\n * Action to select an authentication method.\n * This action is dispatched when the user selects an authentication method\n * (e.g., password, passkey, etc.) from the available options.\n */\n type: \"action_select_method\"\n /**\n * The authentication method that the user has selected.\n */\n method: UiNodeGroupEnum\n }\n | {\n /**\n * Action to clear the active authentication method.\n * This action is dispatched when the user wants to clear the currently active method\n * and return to the method selection state.\n */\n type: \"action_clear_active_method\"\n }\n | {\n /**\n * Action to indicate that an input group is loading.\n * This action is dispatched when the specified input is in the process of loading,\n * and it sets the form state to not ready.\n */\n type: \"form_input_loading\"\n /**\n * The input group that is loading.\n */\n group: UiNodeGroupEnum\n }\n | {\n /**\n * Action to indicate that the input group is ready.\n * This action is dispatched when the specified input has finished loading,\n * and it sets the form state to ready.\n */\n type: \"form_input_ready\"\n /**\n * The input group that is ready.\n */\n input: UiNodeGroupEnum\n }\n | {\n /**\n * Action to indicate the start of a form submission.\n * This action is dispatched when the user submits the form, and it sets the submitting state to true.\n */\n type: \"form_submit_start\"\n }\n | {\n /**\n * Action to indicate the end of a form submission.\n * This action is dispatched when the form submission is complete, and it sets the submitting state to false.\n */\n type: \"form_submit_end\"\n }\n | {\n /**\n * Action to indicate that a page redirect is occurring.\n * This action is dispatched when the form submission results in a page redirect\n * (usually after a successful login, etc. to redirect to the main application's URL),\n * and it keeps the submitting state as true, as the next action is a full page unload.\n *\n * This is necessary, to keep submit buttons in a submitting state while the redirect is in progress,\n * to prevent the user accidentally interacting with the page while it's redirecting causing UX issues.\n */\n type: \"page_redirect\"\n }\n\nfunction findMethodWithMessage(nodes?: UiNode[]) {\n return nodes\n ?.filter((n) => ![\"default\", \"identifier_first\"].includes(n.group))\n ?.find((node) => node.messages?.length > 0)\n}\n\nfunction parseStateFromFlow(flow: OryFlowContainer): FlowFormState {\n switch (flow.flowType) {\n case FlowType.Registration:\n case FlowType.Login: {\n const methodWithMessage = findMethodWithMessage(flow.flow.ui.nodes)\n if (flow.flow.active == \"link_recovery\") {\n return { current: \"method_active\", method: \"link\" }\n } else if (flow.flow.active == \"code_recovery\") {\n return { current: \"method_active\", method: \"code\" }\n } else if (methodWithMessage) {\n return { current: \"method_active\", method: methodWithMessage.group }\n } else if (flow.flow.ui.messages?.some((m) => m.id === 1010016)) {\n // Account linking edge case\n return { current: \"select_method\" }\n } else if (\n flow.flow.active &&\n ![\"default\", \"identifier_first\"].includes(flow.flow.active)\n ) {\n return { current: \"method_active\", method: flow.flow.active }\n } else if (isChoosingMethod(flow)) {\n // Login has a special case where we only have one method. Here, we\n // do not want to display the chooser.\n const authMethods = nodesToAuthMethodGroups(flow.flow.ui.nodes)\n if (\n authMethods.length === 1 &&\n ![\"code\", \"passkey\"].includes(authMethods[0])\n ) {\n // TODO: https://github.com/ory/kratos/issues/4271 - once this is fixed in Kratos, we can remove the check for \"code\"\n return { current: \"method_active\", method: authMethods[0] }\n }\n return { current: \"select_method\" }\n }\n return { current: \"provide_identifier\" }\n }\n case FlowType.Recovery:\n case FlowType.Verification:\n // The API does not provide types for the active field of the recovery flow\n // TODO: Add types for the recovery flow in Kratos\n if (flow.flow.active === \"code\" || flow.flow.active === \"link\") {\n if (flow.flow.state === \"choose_method\") {\n return { current: \"provide_identifier\" }\n }\n return { current: \"method_active\", method: flow.flow.active }\n }\n break\n case FlowType.Settings:\n return { current: \"settings\" }\n case FlowType.OAuth2Consent:\n return { current: \"method_active\", method: \"oauth2_consent\" }\n }\n console.warn(\n `[Ory/Elements React] Encountered an unknown form state on ${flow.flowType} flow with ID ${flow.flow.id}`,\n )\n throw new Error(\"Unknown form state\")\n}\n\n/**\n * The `useFormStateReducer` hook manages the state of the form based on the flow data.\n *\n * It uses a reducer to handle actions that update the form state, such as selecting an authentication method or updating the flow.\n *\n * @see FormState\n * @see FormStateAction\n * @param flow - The flow container that contains the flow data.\n * @returns a tuple containing the current form state and a dispatch function to update the state.\n */\nexport function useFormStateReducer(flow: OryFlowContainer) {\n const action = parseStateFromFlow(flow)\n const [selectedMethod, setSelectedMethod] = useState<\n UiNodeGroupEnum | undefined\n >()\n const [isRedirecting, setRedirecting] = useState(false)\n const [loadingInputs, setLoadingInputs] = useState<Set<UiNodeGroupEnum>>(\n new Set(),\n )\n\n const formStateReducer = (\n state: FormState,\n action: FormStateAction,\n ): FormState => {\n switch (action.type) {\n case \"action_flow_update\": {\n if (selectedMethod) {\n setLoadingInputs(new Set())\n return {\n current: \"method_active\",\n method: selectedMethod,\n isReady: state.isReady,\n isSubmitting: state.isSubmitting,\n }\n }\n const flowFormState = parseStateFromFlow(action.flow)\n return {\n ..