UNPKG

@ory/elements-react

Version:

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

1 lines 570 kB
{"version":3,"sources":["../../../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/theme/default/utils/url.ts","../../../src/util/ui/index.ts","../../../src/context/component.tsx","../../../src/util/nodes.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/assets/icons/arrow-left.svg","../../../src/theme/default/utils/attributes.ts","../../../src/theme/default/components/card/logo.tsx","../../../src/theme/default/components/card/layout.tsx","../../../src/theme/default/components/card/index.tsx","../../../src/theme/default/utils/cn.ts","../../../src/theme/default/components/form/index.tsx","../../../src/theme/default/components/form/social.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/generic.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/util/omitAttributes.ts","../../../src/theme/default/components/card/auth-method-list-item.tsx","../../../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/webauthn.svg","../../../src/theme/default/assets/icons/totp.svg","../../../src/theme/default/assets/icons/code-asterix.svg","../../../src/theme/default/utils/form.ts","../../../src/theme/default/components/card/list-item.tsx","../../../src/theme/default/components/form/button.tsx","../../../src/theme/default/components/form/checkbox.tsx","../../../src/theme/default/components/ui/checkbox-label.tsx","../../../src/util/i18n/index.ts","../../../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/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/logout.ts","../../../src/util/client.ts","../../../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/assets/icons/personal.svg","../../../src/theme/default/assets/icons/message.svg","../../../src/theme/default/assets/icons/phone.svg","../../../src/theme/default/components/form/consent-scope-checkbox.tsx","../../../src/theme/default/components/default-components.tsx","../../../src/theme/default/flows/error.tsx","../../../src/context/intl-context.tsx","../../../src/context/flow-context.tsx","../../../src/context/form-state.ts","../../../src/components/card/card-two-step.utils.ts","../../../src/context/provider.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/components/card/card.tsx","../../../src/components/card/footer.tsx","../../../src/components/card/content.tsx","../../../src/components/card/card-two-step.tsx","../../../src/components/form/form.tsx","../../../src/components/form/useOryFormSubmit.ts","../../../src/util/onSubmitLogin.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/input.tsx","../../../src/components/form/nodes/node.tsx","../../../src/components/form/social.tsx","../../../src/components/form/groups.tsx","../../../src/components/form/section.tsx","../../../src/components/card/card-consent.tsx","../../../src/components/generic/divider.tsx","../../../src/components/generic/page-header.tsx","../../../src/components/settings/settings-card.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/en.json","../../../src/locales/de.json","../../../src/locales/es.json","../../../src/locales/fr.json","../../../src/locales/nl.json","../../../src/locales/pl.json","../../../src/locales/pt.json","../../../src/locales/sv.json","../../../src/locales/index.ts","../../../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":["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=\"#0F172A\" d=\"M18.007 8h-1.71l2.007-3.996L16.296 0h1.711l1.145 2.301L20.327 0H22z\" /><path fill=\"#0F172A\" 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=\"#0F172A\" d=\"M8 3.993v1.71L4.004 3.697 0 5.704V3.993l2.301-1.145L0 1.673V0z\" /><path fill=\"#0F172A\" 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 className=\"absolute bg-ory-background-default p-2 font-bold max-sm:bottom-0 max-sm:left-8 max-sm:translate-y-full max-sm:rounded-b-branding sm:right-0 sm:top-8 sm:translate-x-full sm:rounded-r-branding border-ory-border-default border max-sm:py-[7px] sm:pl-[7px]\">\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\nexport function DefaultCardContent({ children }: OryCardContentProps) {\n return children\n}\n","// Copyright © 2024 Ory Corp\n// SPDX-License-Identifier: Apache-2.0\n\nimport { FlowType, UiNodeInputAttributes } from \"@ory/client-fetch\"\nimport { ConsentFlow, useComponents, useOryFlow } from \"@ory/elements-react\"\nimport { useIntl } from \"react-intl\"\nimport { initFlowUrl, restartFlowUrl } from \"../../utils/url\"\nimport { findNode, nodesToAuthMethodGroups } from \"../../../../util/ui\"\nimport { findScreenSelectionButton } from \"../../../../util/nodes\"\n\nexport function DefaultCardFooter() {\n const oryFlow = useOryFlow()\n switch (oryFlow.flowType) {\n case FlowType.Login:\n return <LoginCardFooter />\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\nexport function getReturnToQueryParam(flow: { return_to?: string }) {\n if (flow.return_to) {\n return flow.return_to\n }\n if (typeof window !== \"undefined\") {\n const searchParams = new URLSearchParams(window.location.search)\n return searchParams.get(\"return_to\")\n }\n}\n\nfunction LoginCardFooter() {\n const { config, formState, flow, flowType } = useOryFlow()\n const intl = useIntl()\n\n const authMethods = nodesToAuthMethodGroups(flow.ui.nodes)\n\n if (flowType === FlowType.Login && flow.refresh) {\n return null\n }\n\n return (\n <>\n {formState.current === \"provide_identifier\" &&\n config.project.registration_enabled && (\n <span className=\"font-normal leading-normal antialiased text-interface-foreground-default-primary\">\n {intl.formatMessage({\n id: \"login.registration-label\",\n defaultMessage: \"No account?\",\n })}{\" \"}\n <a\n className=\"text-button-link-brand-brand transition-colors hover:text-button-link-brand-brand-hover underline\"\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=\"font-normal leading-normal antialiased text-interface-foreground-default-primary\">\n <a\n className=\"text-button-link-brand-brand transition-colors hover:text-button-link-brand-brand-hover underline\"\n href=\"\"\n data-testid={\"ory/screen/login/mfa/action/selectMethod\"}\n >\n {intl.formatMessage({\n id: \"login.2fa.method.go-back\",\n })}\n </a>\n </span>\n )}\n {/* special case for code auth method */}\n {authMethods.length === 1 &&\n authMethods[0] === \"code\" &&\n formState.current === \"method_active\" && (\n <span className=\"font-normal leading-normal antialiased text-interface-foreground-default-primary\">\n <a\n className=\"text-button-link-brand-brand transition-colors hover:text-button-link-brand-brand-hover underline\"\n href={restartFlowUrl(\n flow,\n `${config.sdk.url}/self-service/${flowType}/browser`,\n )}\n data-testid={\"ory/screen/login/mfa/action/reauthenticate\"}\n >\n {intl.formatMessage({\n id: \"login.2fa.go-back.link\",\n })}\n </a>\n </span>\n )}\n {flowType === FlowType.Login &&\n flow.requested_aal === \"aal2\" &&\n (formState.current === \"select_method\" || authMethods.length === 0) && (\n <span className=\"font-normal leading-normal antialiased text-interface-foreground-default-primary\">\n {intl.formatMessage({\n id: \"login.2fa.go-back\",\n })}{\" \"}\n <a\n className=\"text-button-link-brand-brand transition-colors hover:text-button-link-brand-brand-hover underline\"\n href={restartFlowUrl(\n flow,\n `${config.sdk.url}/self-service/${flowType}/browser`,\n )}\n data-testid={\"ory/screen/login/mfa/action/reauthenticate\"}\n >\n {intl.formatMessage({\n id: \"login.2fa.go-back.link\",\n })}\n </a>\n </span>\n )}\n </>\n )\n}\n\nfunction RegistrationCardFooter() {\n const intl = useIntl()\n const { config, flow, formState } = useOryFlow()\n\n const screenSelectionNode = findScreenSelectionButton(flow.ui.nodes)\n switch (formState.current) {\n case \"method_active\":\n return (\n <span className=\"font-normal leading-normal antialiased\">\n {screenSelectionNode && (\n <a\n className=\"font-medium text-button-link-brand-brand hover:text-button-link-brand-brand-hover\"\n href=\"\"\n >\n {intl.formatMessage({\n id: \"card.footer.select-another-method\",\n defaultMessage: \"Select another method\",\n })}\n </a>\n )}\n </span>\n )\n case \"select_method\":\n default:\n return (\n <span className=\"font-normal leading-normal 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 transition-colors hover:text-button-link-brand-brand-hover underline\"\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\ntype ConsentFlowProps = {\n flow: ConsentFlow\n}\n\nfunction ConsentCardFooter({ flow }: ConsentFlowProps) {\n const { Node } = useComponents()\n\n const rememberNode = findNode(flow.ui.nodes, {\n group: \"oauth2_consent\",\n node_type: \"input\",\n name: \"remember\",\n })\n\n return (\n <div className=\"flex gap-8 flex-col\">\n <div>\n <p className=\"text-interface-foreground-default-secondary leading-normal font-medium\">\n Make sure you trust {flow.consent_request.client?.client_name}\n </p>\n <p className=\"text-interface-foreground-default-secondary leading-normal\">\n You may be sharing sensitive information with this site or\n application.\n </p>\n </div>\n {rememberNode && (\n <Node.Checkbox\n attributes={rememberNode.attributes}\n node={rememberNode}\n />\n )}\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-2\">\n {flow.ui.nodes\n .filter(\n (n) =>\n n.attributes.node_type === \"input\" &&\n n.attributes.type === \"submit\",\n )\n .map((n) => {\n const attributes = n.attributes as UiNodeInputAttributes\n return (\n <Node.Button\n key={attributes.value}\n node={n}\n attributes={attributes}\n />\n )\n })}\n </div>\n <p className=\"text-sm\">\n <span className=\"text-interface-foreground-default-tertiary\">\n Authorizing will redirect to{\" \"}\n </span>\n {flow.consent_request.client?.client_name}\n </p>\n </div>\n )\n}\n","// Copyright © 2024 Ory Corp\n// SPDX-License-Identifier: Apache-2.0\n\nimport { AuthenticatorAssuranceLevel } from \"@ory/client-fetch\"\n\nexport function restartFlowUrl(\n flow: {\n id: string\n request_url?: string\n requested_aal?: string\n return_to?: string\n },\n fallback: string,\n) {\n if (flow.requested_aal === \"aal2\")\n return appendRefresh(appendAal(fallback, \"aal1\"), true)\n return flow.request_url || appendReturnTo(fallback, flow.return_to)\n}\n\nexport function initFlowUrl(\n sdkUrl: string,\n flowType: string,\n flow: {\n id: string\n return_to?: string\n oauth2_login_challenge?: string\n },\n) {\n const result = `${sdkUrl}/self-service/${flowType}/browser`\n const qs = new URLSearchParams()\n\n if (flow.oauth2_login_challenge) {\n qs.set(\"login_challenge\", flow.oauth2_login_challenge)\n }\n if (flow.return_to) {\n qs.set(\"return_to\", flow.return_to)\n } else if (typeof window !== \"undefined\") {\n const searchParams = new URLSearchParams(window.location.search)\n if (searchParams.has(\"return_to\")) {\n qs.set(\"return_to\", searchParams.get(\"return_to\") || \"\")\n }\n }\n\n if (qs.toString().length === 0) {\n return result\n }\n\n return result + \"?\" + qs.toString()\n}\n\nfunction appendReturnTo(url: string, returnTo?: string) {\n if (!returnTo) {\n return url\n }\n\n const urlObj = new URL(url)\n urlObj.searchParams.set(\"return_to\", returnTo)\n return urlObj.toString()\n}\n\nfunction appendAal(url: string, aal: AuthenticatorAssuranceLevel) {\n const urlObj = new URL(url)\n urlObj.searchParams.set(\"aal\", aal)\n return urlObj.toString()\n}\n\nfunction appendRefresh(url: string, refresh: boolean) {\n const urlObj = new URL(url)\n urlObj.searchParams.set(\"refresh\", refresh ? \"true\" : \"false\")\n return urlObj.toString()\n}\n","// Copyright © 2024 Ory Corp\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n isUiNodeInputAttributes,\n isUiNodeScriptAttributes,\n UiNode,\n} from \"@ory/client-fetch\"\n\nimport type {\n UiNodeAttributes,\n UiNodeInputAttributesOnclickTriggerEnum,\n UiNodeInputAttributesOnloadTriggerEnum,\n UiNodeInputAttributesTypeEnum,\n} from \"@ory/client-fetch\"\nimport { UiNodeGroupEnum } from \"@ory/client-fetch\"\nimport { useMemo } from \"react\"\nimport { useGroupSorter } from \"../../context/component\"\n\nexport function capitalize(s: string) {\n if (!s) {\n return s\n }\n return s.charAt(0).toUpperCase() + s.slice(1)\n}\n\nexport type FilterNodesByGroups = {\n nodes: UiNode[]\n groups?: UiNodeGroupEnum[] | UiNodeGroupEnum\n withoutDefaultGroup?: boolean\n attributes?: UiNodeInputAttributesTypeEnum[] | UiNodeInputAttributesTypeEnum\n withoutDefaultAttributes?: boolean\n excludeAttributes?:\n | UiNodeInputAttributesTypeEnum[]\n | UiNodeInputAttributesTypeEnum\n}\n\nexport function triggerToWindowCall(\n trigger:\n | UiNodeInputAttributesOnclickTriggerEnum\n | UiNodeInputAttributesOnloadTriggerEnum\n | undefined,\n) {\n if (!trigger) {\n return\n }\n\n const fn = triggerToFunction(trigger)\n if (fn) {\n fn()\n return\n }\n\n // Retry every 100ms for 10 seconds\n let i = 0\n const ms = 100\n const interval = setInterval(() => {\n i++\n if (i > 100) {\n clearInterval(interval)\n throw new Error(\n \"Unable to load Ory's WebAuthn script. Is it being blocked or otherwise failing to load? If you are running an old version of Ory Elements, please upgrade. For more information, please check your browser's developer console.\",\n )\n }\n\n const fn = triggerToFunction(trigger)\n if (fn) {\n clearInterval(interval)\n return fn()\n }\n }, ms)\n return\n}\n\nfunction triggerToFunction(\n trigger:\n | UiNodeInputAttributesOnclickTriggerEnum\n | UiNodeInputAttributesOnloadTriggerEnum,\n) {\n if (typeof window === \"undefined\") {\n console.debug(\n \"The Ory SDK is missing a required function: window is undefined.\",\n )\n return undefined\n }\n\n const typedWindow = window as { [key: string]: any } // eslint-disable-line @typescript-eslint/no-explicit-any\n if (!(trigger in typedWindow) || !typedWindow[trigger]) {\n console.debug(`The Ory SDK is missing a required function: ${trigger}.`)\n return undefined\n }\n const triggerFn = typedWindow[trigger]\n if (typeof triggerFn !== \"function\") {\n console.debug(\n `The Ory SDK is missing a required function: ${trigger}. It is not a function.`,\n )\n return undefined\n }\n return triggerFn as () => void\n}\n\ntype Entries<T> = {\n [K in keyof T]: [K, T[K]]\n}[keyof T][]\n\n/**\n * Returns a list of auth methods from a list of nodes. For example,\n * if Password and Passkey are present, it will return [password, passkey].\n *\n * Please note that OIDC is not considered an auth method because it is\n * usually shown as a separate auth method\n *\n * This method the default, identifier_first, and profile groups.\n *\n * @param nodes - The nodes to extract the auth methods from\n * @param excludeAuthMethods - A list of auth methods to exclude\n */\nexport function nodesToAuthMethodGroups(\n nodes: Array<UiNode>,\n excludeAuthMethods = [],\n): UiNodeGroupEnum[] {\n const groups: Partial<Record<UiNodeGroupEnum, UiNode[]>> = {}\n\n for (const node of nodes) {\n if (node.type === \"script\") {\n // We always render all scripts, because the scripts for passkeys are part of the webauthn group,\n // which leads to this hook returning a webauthn group on passkey flows (which it should not - webauthn is the \"legacy\" passkey implementation).\n continue\n }\n const groupNodes = groups[node.group] ?? []\n groupNodes.push(node)\n groups[node.group] = groupNodes\n }\n\n return Object.values(UiNodeGroupEnum)\n .filter((group) => groups[group]?.length)\n .filter(\n (group) =>\n !(\n [\n UiNodeGroupEnum.Default,\n UiNodeGroupEnum.IdentifierFirst,\n UiNodeGroupEnum.Profile,\n UiNodeGroupEnum.Captcha,\n ...excludeAuthMethods,\n ] as UiNodeGroupEnum[]\n ).includes(group),\n )\n}\n\n/**\n * Groups nodes by their group and returns an object with the groups and entries.\n *\n * @param nodes - The nodes to group\n * @param opts - The options to use\n */\nexport function useNodesGroups(\n nodes: UiNode[],\n { omit }: { omit?: Array<\"script\" | \"input_hidden\"> } = {},\n) {\n const groupSorter = useGroupSorter()\n\n const groups = useMemo(() => {\n const groups: Partial<Record<UiNodeGroupEnum, UiNode[]>> = {}\n const groupRetained: Partial<Record<UiNodeGroupEnum, number>> = {}\n\n for (const node of nodes) {\n const groupNodes = groups[node.group] ?? []\n groupNodes.push(node)\n groups[node.group] = groupNodes\n\n if (\n omit?.includes(\"script\") &&\n isUiNodeScriptAttributes(node.attributes)\n ) {\n continue\n }\n\n if (\n omit?.includes(\"input_hidden\") &&\n isUiNodeInputAttributes(node.attributes) &&\n node.attributes.type === \"hidden\"\n ) {\n continue\n }\n\n groupRetained[node.group] = (groupRetained[node.group] ?? 0) + 1\n }\n\n const finalGroups: Partial<Record<UiNodeGroupEnum, UiNode[]>> = {}\n for (const [group, count] of Object.entries(groupRetained)) {\n if (count > 0) {\n finalGroups[group as UiNodeGroupEnum] = groups[group as UiNodeGroupEnum]\n }\n }\n\n return finalGroups\n }, [nodes, omit])\n\n const entries = useMemo(\n () =>\n (\n Object.entries(groups) as Entries<Record<UiNodeGroupEnum, UiNode[]>>\n ).sort(([a], [b]) => groupSorter(a, b)),\n [groups, groupSorter],\n )\n\n return {\n groups,\n entries,\n }\n}\n\n/**\n * Find a node\n * @param nodes - The list of nodes to search\n * @param opt - The matching options\n * @returns The first matching node\n */\nexport const findNode = <T extends UiNodeAttributes[\"node_type\"]>(\n nodes: UiNode[],\n opt: {\n node_type: T\n group: UiNodeGroupEnum | RegExp\n name?: string | RegExp\n },\n) =>\n nodes.find((n) => {\n return (\n n.attributes.node_type === opt.node_type &&\n (opt.group instanceof RegExp\n ? n.group.match(opt.group)\n : n.group === opt.group) &&\n (opt.name && n.attributes.node_type === \"input\"\n ? opt.name instanceof RegExp\n ? n.attributes.name.match(opt.name)\n : n.attributes.name === opt.name\n : !opt.name)\n )\n }) as\n | (UiNode & { attributes: UiNodeAttributes & { node_type: T } })\n | undefined\n","// Copyright © 2024 Ory Corp\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n isUiNodeInputAttributes,\n UiNode,\n UiNodeGroupEnum,\n} from \"@ory/client-fetch\"\nimport { PropsWithChildren, createContext, useContext } from \"react\"\nimport { OryFlowComponents } from \"../components\"\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\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\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 defaultNodeOrder = [\n \"oidc\",\n \"saml\",\n \"identifier_first\",\n \"default\",\n \"profile\",\n \"password\",\n // CAPTCHA is below password because otherwise the password input field\n // would be above the captcha. Somehow, we sort the password sign up button somewhere else to be always at the bottom.\n \"captcha\",\n \"passkey\",\n \"code\",\n \"webauthn\",\n]\n\nfunction defaultNodeSorter(\n a: UiNode,\n b: UiNode,\n // ctx: { flowType: string },\n): number {\n const aGroupWeight = defaultNodeOrder.indexOf(a.group) ?? 999\n const bGroupWeight = defaultNodeOrder.indexOf(b.group) ?? 999\n\n if (\n b.group === \"captcha\" &&\n isUiNodeInputAttributes(a.attributes) &&\n a.attributes.type === \"submit\"\n ) {\n // If the current node is a submit button and the next node is CAPTCHA; we sort the captcha before the submit button.\n return aGroupWeight - (bGroupWeight - 2)\n } else if (\n a.group === \"captcha\" &&\n isUiNodeInputAttributes(b.attributes) &&\n b.attributes.type === \"submit\"\n ) {\n // If one node is a submit button and the other is CAPTCHA, we sort the captcha before the submit button.\n return aGroupWeight - 2 - bGroupWeight\n }\n\n return aGroupWeight - bGroupWeight\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 © 2025 Ory Corp\n// SPDX-License-Identifier: Apache-2.0\n\nimport { UiNode, UiNodeInputAttributes } from \"@ory/client-fetch\"\n\nexport function findScreenSelectionButton(\n nodes: UiNode[],\n): { attributes: UiNodeInputAttributes } | undefined {\n return nodes.find(\n (node) =>\n node.attributes.node_type === \"input\" &&\n node.attributes.type === \"submit\" &&\n node.attributes.name === \"screen\",\n ) as { attributes: UiNodeInputAttributes }\n}\n","// Copyright © 2024 Ory Corp\n// SPDX-License-Identifier: Apache-2.0\n\nimport { useComponents, useOryFlow } from \"@ory/elements-react\"\nimport { useCardHeaderText } from \"../../utils/constructCardHeader\"\nimport { DefaultCurrentIdentifierButton } from \"./current-identifier-button\"\n\nfunction InnerCardHeader({ title, text }: { title: string; text?: string }) {\n const { Card } = useComponents()\n return (\n <header className=\"flex flex-col gap-8 antialiased\">\n <Card.Logo />\n <div className=\"flex flex-col gap-2\">\n <h2 className=\"text-lg font-semibold leading-normal text-interface-foreground-default-primary\">\n {title}\n </h2>\n <p className=\"leading-normal text-interface-foreground-default-secondary\">\n {text}\n </p>\n <DefaultCurrentIdentifierButton />\n </div>\n </header>\n )\n}\n\nexport function DefaultCardHeader() {\n const context = useOryFlow()\n const { title, description } = useCardHeaderText(context.flow.ui, context)\n\n return <InnerCardHeader title={title} text={description} />\n}\n","// Copyright © 2024 Ory Corp\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n AuthenticatorAssuranceLevel,\n FlowType,\n isUiNodeInputAttributes,\n OAuth2ConsentRequest,\n Session,\n UiContainer,\n} from \"@ory/client-fetch\"\nimport { useIntl } from \"react-intl\"\nimport { FormState } from \"../../../context\"\n\nfunction joinWithCommaOr(list: string[], orText = \"or\"): string {\n if (list.length === 0) {\n return \".\"\n } else if (list.length === 1) {\n return list[0]\n } else {\n const last = list.pop()\n return `${list.join(\", \")} ${orText} ${last}`\n }\n}\n\nexport type CardHeaderTextOptions =\n | {\n flowType: FlowType.Login\n flow: {\n refresh?: boolean\n requested_aal?: AuthenticatorAssuranceLevel\n }\n formState?: FormState\n }\n | {\n flowType: FlowType.OAuth2Consent\n flow: {\n consent_request: OAuth2ConsentRequest\n session: Session\n }\n }\n | {\n flowType:\n | FlowType.Error\n | FlowType.Registration\n | FlowType.Verification\n | FlowType.Recovery\n | FlowType.Settings\n }\n\n/**\n * Constructs a title and a description for the card header.\n *\n * The title is a title suitable for the current flow, e.g. \"Sign in\" or \"Update your account\".\n *\n * The description for a login & registration flow, is a collection of the labels of the input fields.\n * For example, if the user has a password and an email address, the description will be \"Sign in with your email and password\".\n * And for registration, the listed options depend on the project configuration.\n *\n * For verification, recovery and settings flows, the description is a generic one, e.g. \"Enter the email address associated with your account to verify it\".\n *\n *\n * @param nodes - the UI nodes of the current flow\n * @param opts - can be a flow object, only needed for the refresh login flow\n * @returns a title and a description for the card header\n */\nexport function useCardHeaderText(\n container: UiContainer,\n opts: CardHeaderTextOptions,\n): { title: string; description: string } {\n const nodes = container.nodes\n const intl = useIntl()\n switch (opts.flowType) {\n case FlowType.Recovery:\n if (\n nodes.find(\n (node) =>\n \"name\" in node.attributes && node.attributes.name === \"code\",\n )\n ) {\n return {\n title: intl.formatMessage({\n id: \"recovery.title\",\n }),\n description: intl.formatMessage({\n id: \"identities.messages.1060003\",\n }),\n }\n }\n return {\n title: intl.formatMessage({\n id: \"recovery.title\",\n }),\n description: intl.formatMessage({\n id: \"recovery.subtitle\",\n }),\n }\n case FlowType.Settings:\n return {\n title: intl.formatMessage({\n id: \"settings.title\",\n }),\n description: intl.formatMessage({\n id: \"settings.subtitle\",\n }),\n }\n case FlowType.Verification:\n if (\n nodes.find(\n (node) =>\n \"name\" in node.attributes && node.attributes.name === \"code\",\n )\n ) {\n return {\n title: intl.formatMessage({\n id: \"verification.title\",\n }),\n description: intl.formatMessage({\n id: \"identities.messages.1080003\",\n }),\n }\n }\n return {\n title: intl.formatMessage({\n id: \"verification.title\",\n }),\n description: intl.formatMessage({\n id: \"verification.subtitle\",\n }),\n }\n case FlowType.Login: {\n // account linking\n const accountLinkingMessage = container.messages?.find(\n (m) => m.id === 1010016,\n )\n if (accountLinkingMessage) {\n return {\n title: intl.formatMessage({\n id: \"account-linking.title\",\n }),\n description: intl.formatMessage(\n {\n id: \"identities.messages.1010016\",\n },\n accountLinkingMessage.context as Record<string, string>,\n ),\n }\n }\n }\n }\n\n const parts = []\n\n if (nodes.find((node) => node.group === \"password\")) {\n switch (opts.flowType) {\n case FlowType.Registration:\n parts.push(\n intl.formatMessage(\n { id: \"card.header.parts.password.registration\" },\n // TODO: make this generic for other labels\n { identifierLabel: \"email\" },\n ),\n )\n break\n default:\n parts.push(\n intl.formatMessage(\n { id: \"card.header.parts.password.login\" },\n // TODO: make this generic for other labels\n { identifierLabel: \"email\" },\n ),\n )\n }\n }\n\n if (nodes.find((node) => node.group === \"oidc\" || node.group === \"saml\")) {\n parts.push(\n intl.formatMessage({\n id: \"card.header.parts.oidc\",\n }),\n )\n }\n\n if (nodes.find((node) => node.group === \"code\")) {\n parts.push(intl.formatMessage({ id: \"card.header.parts.code\" }))\n }\n\n if (nodes.find((node) => node.group === \"passkey\")) {\n parts.push(intl.formatMessage({ id: \"card.header.parts.passkey\" }))\n }\n\n if (nodes.find((node) => node.group === \"webauthn\")) {\n parts.push(intl.formatMessage({ id: \"card.header.parts.webauthn\" }))\n }\n\n if (nodes.find((node) => node.group === \"identifier_first\")) {\n const identifier = nodes.find(\n (node) =>\n isUiNodeInputAttributes(node.attributes) &&\n node.attributes.name.startsWith(\"identifier\") &&\n node.attributes.type !== \"hidden\",\n )\n\n if (identifier) {\n parts.push(\n intl.formatMessage(\n {\n id: \"card.header.parts.identifier-first\",\n },\n {\n identifierLabel: identifier.meta.label?.text,\n },\n ),\n )\n }\n }\n\n switch (opts.flowType) {\n case FlowType.Login:\n if (opts.flow.refresh) {\n return {\n title: intl.formatMessage({\n id: \"login.title-refresh\",\n }),\n description: intl.formatMessage(\n {\n id: \"login.subtitle-refresh\",\n },\n {\n parts: joinWithCommaOr(parts),\n },\n ),\n }\n } else if (opts.flow.requested_aal === \"aal2\") {\n return {\n title: intl.formatMessage({\n id: \"login.title-aal2\",\n }),\n description: intl.formatMessage({\n id:\n opts.formState?.current === \"method_active\"\n ? `login.${opts.formState.method}.subtitle`\n : \"login.subtitle-aal2\",\n }),\n }\n }\n return {\n title: intl.formatMessage({\n id: \"login.title\",\n }),\n description:\n parts.length > 0\n ? intl.formatMessage(\n {\n id: \"login.subtitle\",\n },\n {\n parts: joinWithCommaOr(\n parts,\n intl.formatMessage({ id: \"misc.or\" }),\n ),\n },\n )\n : \"\",\n }\n case FlowType.Registration:\n return {\n title: intl.formatMessage({\n id: \"registration.title\",\n }),\n description:\n parts.length > 0\n ? intl.formatMessage(\n {\n id: \"registration.subtitle\",\n },\n {\n parts: joinWithCommaOr(\n parts,\n intl.formatMessage({ id: \"misc.or\" }),\n ),\n },\n )\n : \"\",\n }\n case FlowType.OAuth2Consent:\n return {\n title: intl.formatMessage(\n {\n id: \"consent.title\",\n },\n {\n party: opts.flow.consent_request.client?.client_name,\n },\n ),\n description: intl.formatMessage(\n {\n id: \"consent.subtitle\",\n },\n {\n identifier: (opts.flow.session.identity?.traits.email ??\n \"\") as string,\n },\n ),\n }\n }\n\n // TODO: This should not happen, as the switch is exhaustive, but typescript doesn't think so\n return {\n title: \"Error\",\n description: \"An error occurred\",\n }\n}\n","// Copyright © 2024 Ory Corp\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n FlowType,\n isUiNodeInputAttributes,\n UiNode,\n UiNodeInputAttributes,\n} from \"@ory/client-fetch\"\nimport { useOryFlow } from \"@ory/elements-react\"\nimport IconArrowLeft from \"../../assets/icons/arrow-left.svg\"\nimport { omit } from \"../../utils/attributes\"\nimport { restartFlowUrl } from \"../../utils/url\"\nimport { findScreenSelectionButton } from \"../../../../util/nodes\"\nimport { useFormContext } from \"react-hook-form\"\n\nexport function DefaultCurrentIdentifierButton() {\n const { flow, flowType, config, formState } = useOryFlow()\n const { setValue } = useFormContext()\n const ui = flow.ui\n\n if (formState.current === \"provide_identifier\") {\n return null\n }\n\n if (flowType === FlowType.Login && flow.requested_aal === \"aal2\") {\n return null\n }\n\n const nodeBackButton = getBackButtonNodeAttributes(flowType, ui.nodes)\n if (!nodeBackButton) {\n return null\n }\n\n const initFlowUrl = restartFlowUrl(\n flow,\n `${config.sdk.url}/self-service/${flowType}/browser`,\n )\n\n const attributes = omit(nodeBackButton, [\n \"autocomplete\",\n \"node_type\",\n \"maxlength\",\n ])\n\n const screenSelectionNode = findScreenSelectionButton(flow.ui.nodes)\n if (screenSelectionNode) {\n // Kill me. Without this, the form will lose the user input data. Therefore, we need to hack around\n // adding this data here.\n return (\n <form action={flow.ui.action} method={flow.ui.method}>\n {flow.ui.nodes\n .filter((n) => {\n if (isUiNodeInputAttributes(n.attributes)) {\n return n.attributes.type === \"hidden\"\n }\n return false\n })\n .map((n: UiNode) => {\n const attrs = n.attributes as UiNodeInputAttributes\n return (\n <input\n key={attrs.name}\n type=\"hidden\"\n name={attrs.name}\n value={attrs.value}\n />\n )\n })}\n <button\n className={\n \"group inline-flex max-w-full cursor-pointer items-center gap-1 self-start rounded-identifier border px-[11px] py-[5px] transition-colors \" +\n \"border-button-identifier-border-border-default bg-button-identifier-background-default hover:border-button-identifier-border-border-hover hover:bg-button-identifier-background-hover\"\n }\n {...attributes}\n type={\"submit\"}\n onClick={() => {\n setValue(\n screenSelectionNode.attributes.name,\n screenSelectionNode.attributes.value,\n )\n setValue(\"method\", \"profile\")\n }}\n name={screenSelectionNode.attributes.name}\n value={screenSelectionNode.attributes.value}\n title={`Adjust ${nodeBackButton?.value}`}\n data-testid={`ory/screen/${flowType}/action/restart`}\n >\n <span className=\"inline-flex min-h-5 items-center gap-2 overflow-hidden text-ellipsis\">\n <IconArrowLeft\n size={16}\n stroke=\"1\"\n className=\"shrink-0 text-button-identifier-foreground-default group-hover:text-button-identifier-foreground-hover\"\n />\n <span className=\"overflow-hidden text-ellipsis text-nowrap text-sm font-medium text-button-identifier-foreground-default group-hover:text-button-identifier-foreground-hover\">\n {nodeBackButton?.value}\n </span>\n </span>\n </button>\n </form>\n )\n }\n\n return (\n <a\n className={\n \"group inline-flex max-w-full cursor-pointer items-center gap-1 self-start rounded-identifier border px-[11px] py-[5px] transition-colors \" +\n \"border-button-identifier-border-border-default bg-button-identifier-background-default hover:border-button-identifier-border-border-hover hover:bg-button-identifier-background-hover\"\n }\n {...attributes}\n href={initFlowUrl}\n title={`Adjust ${nodeBackButton?.value}`}\n data-testid={`ory/screen/${flowType}/action/restart`}\n >\n <span className=\"inline-flex min-h-5 items-center gap-2 overflow-hidden text-ellipsis\">\n <IconArrowLeft\n size={16}\n stroke=\"1\"\n className=\"shrink-0 text-button-identifier-foreground-default group-hover:text-button-identifier-foreground-hover\"\n />\n <span className=\"overflow-hidden text-ellipsis text-nowrap text-sm font-medium text-button-identifier-foreground-default group-hover:text-button-identifier-foreground-hover\">\n {nodeBackButton?.value}\n </span>\n </span>\n </a>\n )\n}\n\nexport function getBackButtonNodeAttributes(\n flowType: FlowType,\n nodes: UiNode[],\n): UiNodeInputAttributes | undefined {\n let nodeBackButtonAttributes: UiNodeInputAttributes | undefined\n switch (flowType) {\n case FlowType.Login:\n nodeBackButtonAttributes = nodes.find(\n (node) =>\n isUiNodeInputAttributes(node.attributes) &&\n node.attributes.name === \"identifier\" &&\n [\"default\", \"identifier_first\"].includes(node.group),\n )?.attributes as UiNodeInputAttributes | undefined\n break\n case FlowType.Registration:\n nodeBackButtonAttributes = guessRegistrationBackButton(nodes)\n break\n case FlowType.Recovery:\n case FlowType.Verification:\n // Re-use the email node for displaying the email\n nodeBackButtonAttributes = nodes.find(\n (n) =>\n isUiNodeInputAttributes(n.attributes) &&\n n.attributes.name === \"email\",\n )?.attributes as UiNodeInputAttributes | undefined\n break\n }\n\n if (\n nodeBackButtonAttributes?.node_type !== \"input\" ||\n !nodeBackButtonAttributes?.value\n ) {\n return undefined\n }\n\n return nodeBackButtonAttributes\n}\n\nconst backButtonCandiates = [\n \"traits.email\",\n \"traits.username\",\n \"traits.phone_number\",\n]\n\n/**\n * Guesses the back button for registration flows\n *\n * This is based on the list above, and the first node that matches the criteria is returned.\n *\n * The list is most likely not exhaustive yet, and may need to be updated in the future.\n *\n */\nexport function guessRegistrationBackButton(\n uiNodes: UiNode[],\n): UiNodeInputAttributes | undefined {\n return uiNodes.find(\n (node) =>\n isUiNodeInputAttributes(node.attributes) &&\n backButtonCandiates.includes(node.attributes.name) &&\n node.group === \"default\",\n )?.attributes as UiNodeInputAttributes | undefined\n}\n","import * as React from \"react\";\nconst SvgArrowLeft = props => <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 25\" width={props?.width ? props.width : props?.size ?? 20} height={props?.height ? props.height : props?.size ?? 20} {...props}><path stroke=\"currentColor\" strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M5 12.325h14m-14 0 6 6m-6-6 6-6\" /></svg>;\nexport default SvgArrowLeft;","// Copyright © 2024 Ory Corp\n// SPDX-License-Identifier: Apache-2.0\n\nexport function omit<OBJ extends object>(\n obj: OBJ,\n keys: (keyof OBJ)[],\n): Omit<typeof obj, (typeof keys)[number]> {\n const ret = { ...obj }\n for (const key of keys) {\n delete ret[key]\n }\n return ret\n}\n","// Copyright © 2024 Ory Corp\n// SPDX-License-Identifier: Apache-2.0\n\nimport { useOryFlow } from \"@ory/elements-react\"\n\nexport function DefaultCardLogo() {\n const flow = useOryFlow()\n\n if (flow.config.logoUrl) {\n return <img src={flow.config.logoUrl} width={100} height={36} alt=\"Logo\" />\n }\n\n return (\n <h1 className=\"text-xl font-semibold leading-normal text-interface-foreground-default-primary\">\n {flow.config.name}\n </h1>\n )\n}\n","// Copyright © 2024 Ory Corp\n// SPDX-License-Identifier: Apache-2.0\n\nimport { PropsWithChildren } from \"react\"\n\nexport function DefaultCardLayout({ children }: PropsWithChildren) {\n return (\n <main className=\"p-4 pb-8 flex items-center justify-center flex-col gap-8 min-h-screen\">\n {children}\n </main>\n )\n}\n","// Copyright © 2024 Ory Corp\n// SPDX-License-Identifier: Apache-2.0\n\nimport { OryCardProps } 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 { DefaultCardLayout } from \"./layout\"\n\nexport function DefaultCard({ children }: OryCardProps) {\n return (\n <div className=\"flex flex-1 sm:items-center justify-center font-sans items-start w-full sm:w-[480px] sm:max-w-[480px]\">\n <div className=\"relative grid grid-cols-1 gap-8 sm:rounded-cards sm:border border-form-border-default bg-form-background-default px-8 py-12 sm:px-12 sm:py-14 border-b w-full\">\n {children}\