diff --git a/packages/elements-react/src/tests/jest/test-utils.tsx b/packages/elements-react/src/tests/jest/test-utils.tsx index ce3f2185b..e61f0668c 100644 --- a/packages/elements-react/src/tests/jest/test-utils.tsx +++ b/packages/elements-react/src/tests/jest/test-utils.tsx @@ -2,6 +2,7 @@ import { PropsWithChildren, ReactElement } from "react" import { OryComponentProvider } from "../../context" import { OryDefaultComponents } from "../../theme/default" import { render, RenderOptions } from "@testing-library/react" +import { OryClientConfiguration } from "../../util" const AllProviders = ({ children }: PropsWithChildren) => ( @@ -14,5 +15,21 @@ const customRender = ( options?: Omit, ) => render(ui, { wrapper: AllProviders, ...options }) +export const defaultConfiguration: OryClientConfiguration = { + name: "test", + project: { + login_ui_url: "http://localhost:4455/login", + recovery_ui_url: "http://localhost:4455/recovery", + registration_ui_url: "http://localhost:4455/registration", + verification_ui_url: "http://localhost:4455/verification", + recovery_enabled: true, + registration_enabled: true, + verification_enabled: true, + }, + sdk: { + url: "http://localhost:4455", + }, +} + export * from "@testing-library/react" export { customRender as render } diff --git a/packages/elements-react/src/theme/default/components/card/header.tsx b/packages/elements-react/src/theme/default/components/card/header.tsx index 21a624434..f360ea731 100644 --- a/packages/elements-react/src/theme/default/components/card/header.tsx +++ b/packages/elements-react/src/theme/default/components/card/header.tsx @@ -1,98 +1,5 @@ -import { FlowType, isUiNodeInputAttributes } from "@ory/client-fetch" -import { FlowContainer, useComponents, useOryFlow } from "@ory/elements-react" - -function joinWithCommaOr(list: string[]): string { - if (list.length === 0) { - return "." - } else if (list.length === 1) { - return list[0] + "." - } else { - const last = list.pop() - return `${list.join(", ")} or ${last}.` - } -} - -function constructCardHeaderText(container: FlowContainer) { - const parts = [] - const ui = container.flow.ui - - if (ui.nodes.find((node) => node.group === "password")) { - switch (container.flowType) { - case FlowType.Registration: - parts.push(`your email and a password`) - break - default: - parts.push(`your email and password`) - } - } - if (ui.nodes.find((node) => node.group === "oidc")) { - parts.push(`a social provider`) - } - - if (ui.nodes.find((node) => node.group === "code")) { - parts.push(`a code sent to your email`) - } - - // TODO - PassKeys - if (ui.nodes.find((node) => node.group === "passkey")) { - parts.push(`a PassKey`) - } - - if (ui.nodes.find((node) => node.group === "webauthn")) { - parts.push(`a security key`) - } - - if (ui.nodes.find((node) => node.group === "default")) { - const primaryIdentifier = ui.nodes.filter( - (node) => - isUiNodeInputAttributes(node.attributes) && - node.attributes.name.startsWith("traits"), - ) - if (primaryIdentifier.length == 1) { - const label = primaryIdentifier[0].meta.label - if (label) { - parts.push(`your ${label.text}`) - } - } - } - - switch (container.flowType) { - case FlowType.Login: - if (container.flow.refresh) { - return { - title: "Reauthenticate", - description: "Confirm your identity with " + joinWithCommaOr(parts), - } - } - return { - title: "Sign in", - description: - parts.length > 0 ? "Sign in with " + joinWithCommaOr(parts) : "", - } - case FlowType.Registration: - return { - title: "Sign up", - description: - parts.length > 0 ? "Sign up with " + joinWithCommaOr(parts) : "", - } - case FlowType.Recovery: - return { - title: "Recover your account", - description: - "Enter the email address associated with your account to receive a one-time access code", - } - case FlowType.Settings: - return { - title: "Update your account", - } - case FlowType.Verification: - return { - title: "Verify your account", - description: - "Enter the email address associated with your account to verify it", - } - } -} +import { useComponents, useOryFlow } from "@ory/elements-react" +import { constructCardHeaderText } from "../../utils/constructCardHeader" function InnerCardHeader({ title, text }: { title: string; text?: string }) { const { CardLogo } = useComponents() @@ -111,7 +18,10 @@ function InnerCardHeader({ title, text }: { title: string; text?: string }) { export function DefaultCardHeader() { const context = useOryFlow() - const { title, description } = constructCardHeaderText(context) + const { title, description } = constructCardHeaderText( + context.flow.ui.nodes, + context, + ) return } diff --git a/packages/elements-react/src/theme/default/utils/__tests__/__snapshots__/constructCardHeader.spec.ts.snap b/packages/elements-react/src/theme/default/utils/__tests__/__snapshots__/constructCardHeader.spec.ts.snap new file mode 100644 index 000000000..726a1b826 --- /dev/null +++ b/packages/elements-react/src/theme/default/utils/__tests__/__snapshots__/constructCardHeader.spec.ts.snap @@ -0,0 +1,981 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`flowType=login refresh=false combination=code constructCardHeaderText 1`] = ` +{ + "description": "Sign in with a code sent to your email.", + "title": "Sign in", +} +`; + +exports[`flowType=login refresh=false combination=default_and_code constructCardHeaderText 1`] = ` +{ + "description": "Sign in with a code sent to your email.", + "title": "Sign in", +} +`; + +exports[`flowType=login refresh=false combination=default_and_oidc constructCardHeaderText 1`] = ` +{ + "description": "Sign in with a social provider.", + "title": "Sign in", +} +`; + +exports[`flowType=login refresh=false combination=default_and_passkeys constructCardHeaderText 1`] = ` +{ + "description": "Sign in with a Passkey.", + "title": "Sign in", +} +`; + +exports[`flowType=login refresh=false combination=default_and_password constructCardHeaderText 1`] = ` +{ + "description": "Sign in with your email and password.", + "title": "Sign in", +} +`; + +exports[`flowType=login refresh=false combination=default_and_webauthn constructCardHeaderText 1`] = ` +{ + "description": "Sign in with a security key.", + "title": "Sign in", +} +`; + +exports[`flowType=login refresh=false combination=defaultGroup constructCardHeaderText 1`] = ` +{ + "description": "", + "title": "Sign in", +} +`; + +exports[`flowType=login refresh=false combination=idenfier_first_and_oidc constructCardHeaderText 1`] = ` +{ + "description": "Sign in with a social provider or your undefined.", + "title": "Sign in", +} +`; + +exports[`flowType=login refresh=false combination=identifierFirst constructCardHeaderText 1`] = ` +{ + "description": "Sign in with your undefined.", + "title": "Sign in", +} +`; + +exports[`flowType=login refresh=false combination=identifierFirstPhone constructCardHeaderText 1`] = ` +{ + "description": "Sign in with your undefined.", + "title": "Sign in", +} +`; + +exports[`flowType=login refresh=false combination=oidc constructCardHeaderText 1`] = ` +{ + "description": "Sign in with a social provider.", + "title": "Sign in", +} +`; + +exports[`flowType=login refresh=false combination=passkey constructCardHeaderText 1`] = ` +{ + "description": "Sign in with a Passkey.", + "title": "Sign in", +} +`; + +exports[`flowType=login refresh=false combination=password constructCardHeaderText 1`] = ` +{ + "description": "Sign in with your email and password.", + "title": "Sign in", +} +`; + +exports[`flowType=login refresh=false combination=webauthn constructCardHeaderText 1`] = ` +{ + "description": "Sign in with a security key.", + "title": "Sign in", +} +`; + +exports[`flowType=login refresh=true combination=code constructCardHeaderText 1`] = ` +{ + "description": "Confirm your identity with a code sent to your email.", + "title": "Reauthenticate", +} +`; + +exports[`flowType=login refresh=true combination=default_and_code constructCardHeaderText 1`] = ` +{ + "description": "Confirm your identity with a code sent to your email.", + "title": "Reauthenticate", +} +`; + +exports[`flowType=login refresh=true combination=default_and_oidc constructCardHeaderText 1`] = ` +{ + "description": "Confirm your identity with a social provider.", + "title": "Reauthenticate", +} +`; + +exports[`flowType=login refresh=true combination=default_and_passkeys constructCardHeaderText 1`] = ` +{ + "description": "Confirm your identity with a Passkey.", + "title": "Reauthenticate", +} +`; + +exports[`flowType=login refresh=true combination=default_and_password constructCardHeaderText 1`] = ` +{ + "description": "Confirm your identity with your email and password.", + "title": "Reauthenticate", +} +`; + +exports[`flowType=login refresh=true combination=default_and_webauthn constructCardHeaderText 1`] = ` +{ + "description": "Confirm your identity with a security key.", + "title": "Reauthenticate", +} +`; + +exports[`flowType=login refresh=true combination=defaultGroup constructCardHeaderText 1`] = ` +{ + "description": "Confirm your identity with .", + "title": "Reauthenticate", +} +`; + +exports[`flowType=login refresh=true combination=idenfier_first_and_oidc constructCardHeaderText 1`] = ` +{ + "description": "Confirm your identity with a social provider or your undefined.", + "title": "Reauthenticate", +} +`; + +exports[`flowType=login refresh=true combination=identifierFirst constructCardHeaderText 1`] = ` +{ + "description": "Confirm your identity with your undefined.", + "title": "Reauthenticate", +} +`; + +exports[`flowType=login refresh=true combination=identifierFirstPhone constructCardHeaderText 1`] = ` +{ + "description": "Confirm your identity with your undefined.", + "title": "Reauthenticate", +} +`; + +exports[`flowType=login refresh=true combination=oidc constructCardHeaderText 1`] = ` +{ + "description": "Confirm your identity with a social provider.", + "title": "Reauthenticate", +} +`; + +exports[`flowType=login refresh=true combination=passkey constructCardHeaderText 1`] = ` +{ + "description": "Confirm your identity with a Passkey.", + "title": "Reauthenticate", +} +`; + +exports[`flowType=login refresh=true combination=password constructCardHeaderText 1`] = ` +{ + "description": "Confirm your identity with your email and password.", + "title": "Reauthenticate", +} +`; + +exports[`flowType=login refresh=true combination=webauthn constructCardHeaderText 1`] = ` +{ + "description": "Confirm your identity with a security key.", + "title": "Reauthenticate", +} +`; + +exports[`flowType=recovery refresh=false combination=code constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to receive a one-time access code", + "title": "Recover your account", +} +`; + +exports[`flowType=recovery refresh=false combination=default_and_code constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to receive a one-time access code", + "title": "Recover your account", +} +`; + +exports[`flowType=recovery refresh=false combination=default_and_oidc constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to receive a one-time access code", + "title": "Recover your account", +} +`; + +exports[`flowType=recovery refresh=false combination=default_and_passkeys constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to receive a one-time access code", + "title": "Recover your account", +} +`; + +exports[`flowType=recovery refresh=false combination=default_and_password constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to receive a one-time access code", + "title": "Recover your account", +} +`; + +exports[`flowType=recovery refresh=false combination=default_and_webauthn constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to receive a one-time access code", + "title": "Recover your account", +} +`; + +exports[`flowType=recovery refresh=false combination=defaultGroup constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to receive a one-time access code", + "title": "Recover your account", +} +`; + +exports[`flowType=recovery refresh=false combination=idenfier_first_and_oidc constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to receive a one-time access code", + "title": "Recover your account", +} +`; + +exports[`flowType=recovery refresh=false combination=identifierFirst constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to receive a one-time access code", + "title": "Recover your account", +} +`; + +exports[`flowType=recovery refresh=false combination=identifierFirstPhone constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to receive a one-time access code", + "title": "Recover your account", +} +`; + +exports[`flowType=recovery refresh=false combination=oidc constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to receive a one-time access code", + "title": "Recover your account", +} +`; + +exports[`flowType=recovery refresh=false combination=passkey constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to receive a one-time access code", + "title": "Recover your account", +} +`; + +exports[`flowType=recovery refresh=false combination=password constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to receive a one-time access code", + "title": "Recover your account", +} +`; + +exports[`flowType=recovery refresh=false combination=webauthn constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to receive a one-time access code", + "title": "Recover your account", +} +`; + +exports[`flowType=recovery refresh=true combination=code constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to receive a one-time access code", + "title": "Recover your account", +} +`; + +exports[`flowType=recovery refresh=true combination=default_and_code constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to receive a one-time access code", + "title": "Recover your account", +} +`; + +exports[`flowType=recovery refresh=true combination=default_and_oidc constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to receive a one-time access code", + "title": "Recover your account", +} +`; + +exports[`flowType=recovery refresh=true combination=default_and_passkeys constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to receive a one-time access code", + "title": "Recover your account", +} +`; + +exports[`flowType=recovery refresh=true combination=default_and_password constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to receive a one-time access code", + "title": "Recover your account", +} +`; + +exports[`flowType=recovery refresh=true combination=default_and_webauthn constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to receive a one-time access code", + "title": "Recover your account", +} +`; + +exports[`flowType=recovery refresh=true combination=defaultGroup constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to receive a one-time access code", + "title": "Recover your account", +} +`; + +exports[`flowType=recovery refresh=true combination=idenfier_first_and_oidc constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to receive a one-time access code", + "title": "Recover your account", +} +`; + +exports[`flowType=recovery refresh=true combination=identifierFirst constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to receive a one-time access code", + "title": "Recover your account", +} +`; + +exports[`flowType=recovery refresh=true combination=identifierFirstPhone constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to receive a one-time access code", + "title": "Recover your account", +} +`; + +exports[`flowType=recovery refresh=true combination=oidc constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to receive a one-time access code", + "title": "Recover your account", +} +`; + +exports[`flowType=recovery refresh=true combination=passkey constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to receive a one-time access code", + "title": "Recover your account", +} +`; + +exports[`flowType=recovery refresh=true combination=password constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to receive a one-time access code", + "title": "Recover your account", +} +`; + +exports[`flowType=recovery refresh=true combination=webauthn constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to receive a one-time access code", + "title": "Recover your account", +} +`; + +exports[`flowType=registration refresh=false combination=code constructCardHeaderText 1`] = ` +{ + "description": "Sign up with a code sent to your email.", + "title": "Sign up", +} +`; + +exports[`flowType=registration refresh=false combination=default_and_code constructCardHeaderText 1`] = ` +{ + "description": "Sign up with a code sent to your email.", + "title": "Sign up", +} +`; + +exports[`flowType=registration refresh=false combination=default_and_oidc constructCardHeaderText 1`] = ` +{ + "description": "Sign up with a social provider.", + "title": "Sign up", +} +`; + +exports[`flowType=registration refresh=false combination=default_and_passkeys constructCardHeaderText 1`] = ` +{ + "description": "Sign up with a Passkey.", + "title": "Sign up", +} +`; + +exports[`flowType=registration refresh=false combination=default_and_password constructCardHeaderText 1`] = ` +{ + "description": "Sign up with your email and a password.", + "title": "Sign up", +} +`; + +exports[`flowType=registration refresh=false combination=default_and_webauthn constructCardHeaderText 1`] = ` +{ + "description": "Sign up with a security key.", + "title": "Sign up", +} +`; + +exports[`flowType=registration refresh=false combination=defaultGroup constructCardHeaderText 1`] = ` +{ + "description": "", + "title": "Sign up", +} +`; + +exports[`flowType=registration refresh=false combination=idenfier_first_and_oidc constructCardHeaderText 1`] = ` +{ + "description": "Sign up with a social provider or your undefined.", + "title": "Sign up", +} +`; + +exports[`flowType=registration refresh=false combination=identifierFirst constructCardHeaderText 1`] = ` +{ + "description": "Sign up with your undefined.", + "title": "Sign up", +} +`; + +exports[`flowType=registration refresh=false combination=identifierFirstPhone constructCardHeaderText 1`] = ` +{ + "description": "Sign up with your undefined.", + "title": "Sign up", +} +`; + +exports[`flowType=registration refresh=false combination=oidc constructCardHeaderText 1`] = ` +{ + "description": "Sign up with a social provider.", + "title": "Sign up", +} +`; + +exports[`flowType=registration refresh=false combination=passkey constructCardHeaderText 1`] = ` +{ + "description": "Sign up with a Passkey.", + "title": "Sign up", +} +`; + +exports[`flowType=registration refresh=false combination=password constructCardHeaderText 1`] = ` +{ + "description": "Sign up with your email and a password.", + "title": "Sign up", +} +`; + +exports[`flowType=registration refresh=false combination=webauthn constructCardHeaderText 1`] = ` +{ + "description": "Sign up with a security key.", + "title": "Sign up", +} +`; + +exports[`flowType=registration refresh=true combination=code constructCardHeaderText 1`] = ` +{ + "description": "Sign up with a code sent to your email.", + "title": "Sign up", +} +`; + +exports[`flowType=registration refresh=true combination=default_and_code constructCardHeaderText 1`] = ` +{ + "description": "Sign up with a code sent to your email.", + "title": "Sign up", +} +`; + +exports[`flowType=registration refresh=true combination=default_and_oidc constructCardHeaderText 1`] = ` +{ + "description": "Sign up with a social provider.", + "title": "Sign up", +} +`; + +exports[`flowType=registration refresh=true combination=default_and_passkeys constructCardHeaderText 1`] = ` +{ + "description": "Sign up with a Passkey.", + "title": "Sign up", +} +`; + +exports[`flowType=registration refresh=true combination=default_and_password constructCardHeaderText 1`] = ` +{ + "description": "Sign up with your email and a password.", + "title": "Sign up", +} +`; + +exports[`flowType=registration refresh=true combination=default_and_webauthn constructCardHeaderText 1`] = ` +{ + "description": "Sign up with a security key.", + "title": "Sign up", +} +`; + +exports[`flowType=registration refresh=true combination=defaultGroup constructCardHeaderText 1`] = ` +{ + "description": "", + "title": "Sign up", +} +`; + +exports[`flowType=registration refresh=true combination=idenfier_first_and_oidc constructCardHeaderText 1`] = ` +{ + "description": "Sign up with a social provider or your undefined.", + "title": "Sign up", +} +`; + +exports[`flowType=registration refresh=true combination=identifierFirst constructCardHeaderText 1`] = ` +{ + "description": "Sign up with your undefined.", + "title": "Sign up", +} +`; + +exports[`flowType=registration refresh=true combination=identifierFirstPhone constructCardHeaderText 1`] = ` +{ + "description": "Sign up with your undefined.", + "title": "Sign up", +} +`; + +exports[`flowType=registration refresh=true combination=oidc constructCardHeaderText 1`] = ` +{ + "description": "Sign up with a social provider.", + "title": "Sign up", +} +`; + +exports[`flowType=registration refresh=true combination=passkey constructCardHeaderText 1`] = ` +{ + "description": "Sign up with a Passkey.", + "title": "Sign up", +} +`; + +exports[`flowType=registration refresh=true combination=password constructCardHeaderText 1`] = ` +{ + "description": "Sign up with your email and a password.", + "title": "Sign up", +} +`; + +exports[`flowType=registration refresh=true combination=webauthn constructCardHeaderText 1`] = ` +{ + "description": "Sign up with a security key.", + "title": "Sign up", +} +`; + +exports[`flowType=settings refresh=false combination=code constructCardHeaderText 1`] = ` +{ + "description": "Update your account settings", + "title": "Update your account", +} +`; + +exports[`flowType=settings refresh=false combination=default_and_code constructCardHeaderText 1`] = ` +{ + "description": "Update your account settings", + "title": "Update your account", +} +`; + +exports[`flowType=settings refresh=false combination=default_and_oidc constructCardHeaderText 1`] = ` +{ + "description": "Update your account settings", + "title": "Update your account", +} +`; + +exports[`flowType=settings refresh=false combination=default_and_passkeys constructCardHeaderText 1`] = ` +{ + "description": "Update your account settings", + "title": "Update your account", +} +`; + +exports[`flowType=settings refresh=false combination=default_and_password constructCardHeaderText 1`] = ` +{ + "description": "Update your account settings", + "title": "Update your account", +} +`; + +exports[`flowType=settings refresh=false combination=default_and_webauthn constructCardHeaderText 1`] = ` +{ + "description": "Update your account settings", + "title": "Update your account", +} +`; + +exports[`flowType=settings refresh=false combination=defaultGroup constructCardHeaderText 1`] = ` +{ + "description": "Update your account settings", + "title": "Update your account", +} +`; + +exports[`flowType=settings refresh=false combination=idenfier_first_and_oidc constructCardHeaderText 1`] = ` +{ + "description": "Update your account settings", + "title": "Update your account", +} +`; + +exports[`flowType=settings refresh=false combination=identifierFirst constructCardHeaderText 1`] = ` +{ + "description": "Update your account settings", + "title": "Update your account", +} +`; + +exports[`flowType=settings refresh=false combination=identifierFirstPhone constructCardHeaderText 1`] = ` +{ + "description": "Update your account settings", + "title": "Update your account", +} +`; + +exports[`flowType=settings refresh=false combination=oidc constructCardHeaderText 1`] = ` +{ + "description": "Update your account settings", + "title": "Update your account", +} +`; + +exports[`flowType=settings refresh=false combination=passkey constructCardHeaderText 1`] = ` +{ + "description": "Update your account settings", + "title": "Update your account", +} +`; + +exports[`flowType=settings refresh=false combination=password constructCardHeaderText 1`] = ` +{ + "description": "Update your account settings", + "title": "Update your account", +} +`; + +exports[`flowType=settings refresh=false combination=webauthn constructCardHeaderText 1`] = ` +{ + "description": "Update your account settings", + "title": "Update your account", +} +`; + +exports[`flowType=settings refresh=true combination=code constructCardHeaderText 1`] = ` +{ + "description": "Update your account settings", + "title": "Update your account", +} +`; + +exports[`flowType=settings refresh=true combination=default_and_code constructCardHeaderText 1`] = ` +{ + "description": "Update your account settings", + "title": "Update your account", +} +`; + +exports[`flowType=settings refresh=true combination=default_and_oidc constructCardHeaderText 1`] = ` +{ + "description": "Update your account settings", + "title": "Update your account", +} +`; + +exports[`flowType=settings refresh=true combination=default_and_passkeys constructCardHeaderText 1`] = ` +{ + "description": "Update your account settings", + "title": "Update your account", +} +`; + +exports[`flowType=settings refresh=true combination=default_and_password constructCardHeaderText 1`] = ` +{ + "description": "Update your account settings", + "title": "Update your account", +} +`; + +exports[`flowType=settings refresh=true combination=default_and_webauthn constructCardHeaderText 1`] = ` +{ + "description": "Update your account settings", + "title": "Update your account", +} +`; + +exports[`flowType=settings refresh=true combination=defaultGroup constructCardHeaderText 1`] = ` +{ + "description": "Update your account settings", + "title": "Update your account", +} +`; + +exports[`flowType=settings refresh=true combination=idenfier_first_and_oidc constructCardHeaderText 1`] = ` +{ + "description": "Update your account settings", + "title": "Update your account", +} +`; + +exports[`flowType=settings refresh=true combination=identifierFirst constructCardHeaderText 1`] = ` +{ + "description": "Update your account settings", + "title": "Update your account", +} +`; + +exports[`flowType=settings refresh=true combination=identifierFirstPhone constructCardHeaderText 1`] = ` +{ + "description": "Update your account settings", + "title": "Update your account", +} +`; + +exports[`flowType=settings refresh=true combination=oidc constructCardHeaderText 1`] = ` +{ + "description": "Update your account settings", + "title": "Update your account", +} +`; + +exports[`flowType=settings refresh=true combination=passkey constructCardHeaderText 1`] = ` +{ + "description": "Update your account settings", + "title": "Update your account", +} +`; + +exports[`flowType=settings refresh=true combination=password constructCardHeaderText 1`] = ` +{ + "description": "Update your account settings", + "title": "Update your account", +} +`; + +exports[`flowType=settings refresh=true combination=webauthn constructCardHeaderText 1`] = ` +{ + "description": "Update your account settings", + "title": "Update your account", +} +`; + +exports[`flowType=verification refresh=false combination=code constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to verify it", + "title": "Verify your account", +} +`; + +exports[`flowType=verification refresh=false combination=default_and_code constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to verify it", + "title": "Verify your account", +} +`; + +exports[`flowType=verification refresh=false combination=default_and_oidc constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to verify it", + "title": "Verify your account", +} +`; + +exports[`flowType=verification refresh=false combination=default_and_passkeys constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to verify it", + "title": "Verify your account", +} +`; + +exports[`flowType=verification refresh=false combination=default_and_password constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to verify it", + "title": "Verify your account", +} +`; + +exports[`flowType=verification refresh=false combination=default_and_webauthn constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to verify it", + "title": "Verify your account", +} +`; + +exports[`flowType=verification refresh=false combination=defaultGroup constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to verify it", + "title": "Verify your account", +} +`; + +exports[`flowType=verification refresh=false combination=idenfier_first_and_oidc constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to verify it", + "title": "Verify your account", +} +`; + +exports[`flowType=verification refresh=false combination=identifierFirst constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to verify it", + "title": "Verify your account", +} +`; + +exports[`flowType=verification refresh=false combination=identifierFirstPhone constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to verify it", + "title": "Verify your account", +} +`; + +exports[`flowType=verification refresh=false combination=oidc constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to verify it", + "title": "Verify your account", +} +`; + +exports[`flowType=verification refresh=false combination=passkey constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to verify it", + "title": "Verify your account", +} +`; + +exports[`flowType=verification refresh=false combination=password constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to verify it", + "title": "Verify your account", +} +`; + +exports[`flowType=verification refresh=false combination=webauthn constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to verify it", + "title": "Verify your account", +} +`; + +exports[`flowType=verification refresh=true combination=code constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to verify it", + "title": "Verify your account", +} +`; + +exports[`flowType=verification refresh=true combination=default_and_code constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to verify it", + "title": "Verify your account", +} +`; + +exports[`flowType=verification refresh=true combination=default_and_oidc constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to verify it", + "title": "Verify your account", +} +`; + +exports[`flowType=verification refresh=true combination=default_and_passkeys constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to verify it", + "title": "Verify your account", +} +`; + +exports[`flowType=verification refresh=true combination=default_and_password constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to verify it", + "title": "Verify your account", +} +`; + +exports[`flowType=verification refresh=true combination=default_and_webauthn constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to verify it", + "title": "Verify your account", +} +`; + +exports[`flowType=verification refresh=true combination=defaultGroup constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to verify it", + "title": "Verify your account", +} +`; + +exports[`flowType=verification refresh=true combination=idenfier_first_and_oidc constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to verify it", + "title": "Verify your account", +} +`; + +exports[`flowType=verification refresh=true combination=identifierFirst constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to verify it", + "title": "Verify your account", +} +`; + +exports[`flowType=verification refresh=true combination=identifierFirstPhone constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to verify it", + "title": "Verify your account", +} +`; + +exports[`flowType=verification refresh=true combination=oidc constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to verify it", + "title": "Verify your account", +} +`; + +exports[`flowType=verification refresh=true combination=passkey constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to verify it", + "title": "Verify your account", +} +`; + +exports[`flowType=verification refresh=true combination=password constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to verify it", + "title": "Verify your account", +} +`; + +exports[`flowType=verification refresh=true combination=webauthn constructCardHeaderText 1`] = ` +{ + "description": "Enter the email address associated with your account to verify it", + "title": "Verify your account", +} +`; diff --git a/packages/elements-react/src/theme/default/utils/__tests__/constructCardHeader.spec.ts b/packages/elements-react/src/theme/default/utils/__tests__/constructCardHeader.spec.ts new file mode 100644 index 000000000..04fdb817b --- /dev/null +++ b/packages/elements-react/src/theme/default/utils/__tests__/constructCardHeader.spec.ts @@ -0,0 +1,186 @@ +// Copyright © 2024 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +import { FlowType, UiNode, UiTextTypeEnum } from "@ory/client-fetch" +import { constructCardHeaderText } from "../constructCardHeader" + +const password: UiNode = { + group: "password", + type: "text", + attributes: { + node_type: "input", + name: "password", + type: "password", + value: "", + disabled: false, + }, + messages: [], + meta: {}, +} + +const oidc: UiNode = { + group: "oidc", + type: "text", + attributes: { + node_type: "input", + name: "oidc", + type: "hidden", + value: "", + disabled: false, + }, + messages: [], + meta: {}, +} + +const code: UiNode = { + group: "code", + type: "text", + attributes: { + node_type: "input", + name: "code", + type: "text", + value: "", + disabled: false, + }, + messages: [], + meta: {}, +} + +const passkey: UiNode = { + group: "passkey", + type: "text", + attributes: { + node_type: "input", + name: "passkey", + type: "text", + value: "", + disabled: false, + }, + messages: [], + meta: {}, +} + +const webauthn: UiNode = { + group: "webauthn", + type: "text", + attributes: { + node_type: "input", + name: "webauthn", + type: "text", + value: "", + disabled: false, + }, + messages: [], + meta: {}, +} + +const defaultGroup: UiNode = { + group: "default", + type: "input", + attributes: { + node_type: "input", + name: "traits.email", + type: "text", + value: "", + disabled: false, + label: { + text: "Email", + type: UiTextTypeEnum.Info, + id: 9999, + }, + }, + messages: [], + meta: { + label: { + text: "Email", + type: UiTextTypeEnum.Info, + id: 9999, + }, + }, +} + +const identifierFirst: UiNode = { + group: "identifier_first", + type: "input", + attributes: { + node_type: "input", + name: "identifier_first", + type: "text", + value: "", + disabled: false, + label: { + text: "Email", + type: UiTextTypeEnum.Info, + id: 9999, + }, + }, + messages: [], + meta: {}, +} + +const identifierFirstPhone: UiNode = { + group: "identifier_first", + type: "input", + attributes: { + node_type: "input", + name: "identifier_first", + type: "text", + value: "", + disabled: false, + label: { + text: "Phone number", + type: UiTextTypeEnum.Info, + id: 9999, + }, + }, + messages: [], + meta: {}, +} + +const combinations = { + oidc, + code, + passkey, + webauthn, + defaultGroup, + identifierFirst, + password, + identifierFirstPhone, + default_and_password: [defaultGroup, password], + default_and_code: [defaultGroup, code], + default_and_oidc: [defaultGroup, oidc], + default_and_passkeys: [defaultGroup, passkey], + default_and_webauthn: [defaultGroup, webauthn], + idenfier_first_and_oidc: [identifierFirst, oidc], +} + +for (const flowType of [ + FlowType.Login, + FlowType.Registration, + FlowType.Verification, + FlowType.Recovery, + FlowType.Settings, +]) { + describe("flowType=" + flowType, () => { + for (const refresh of [true, false]) { + // Yes, it doesn't make sense to test other flows with refresh enabled, but it doesn't hurt, and typescript is dumb here. + describe("refresh=" + refresh, () => { + const opts = { + flowType, + flow: { refresh }, + } + for (const [key, value] of Object.entries(combinations)) { + describe("combination=" + key, () => { + test("constructCardHeaderText", () => { + const res = constructCardHeaderText( + Array.isArray(value) ? value : [value], + opts, + ) + expect(res).toMatchSnapshot() + }) + }) + } + }) + } + }) +} diff --git a/packages/elements-react/src/theme/default/utils/constructCardHeader.ts b/packages/elements-react/src/theme/default/utils/constructCardHeader.ts new file mode 100644 index 000000000..5f07b1e7e --- /dev/null +++ b/packages/elements-react/src/theme/default/utils/constructCardHeader.ts @@ -0,0 +1,139 @@ +// Copyright © 2024 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +import { FlowType, isUiNodeInputAttributes, UiNode } from "@ory/client-fetch" + +function joinWithCommaOr(list: string[]): string { + if (list.length === 0) { + return "." + } else if (list.length === 1) { + return list[0] + "." + } else { + const last = list.pop() + return `${list.join(", ")} or ${last}.` + } +} + +type opts = + | { + flowType: FlowType.Login + flow: { + refresh?: boolean + } + } + | { + flowType: + | FlowType.Error + | FlowType.Registration + | FlowType.Verification + | FlowType.Recovery + | FlowType.Settings + } + +/** + * Constructs a title and a description for the card header. + * + * The title is a title suitable for the current flow, e.g. "Sign in" or "Update your account". + * + * The description for a login & registration flow, is a collection of the labels of the input fields. + * For example, if the user has a password and an email address, the description will be "Sign in with your email and password". + * And for registration, the listed options depend on the project configuration. + * + * 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". + * + * + * @param nodes - the UI nodes of the current flow + * @param opts - can be a flow object, only needed for the refresh login flow + * @returns a title and a description for the card header + */ +export function constructCardHeaderText( + nodes: UiNode[], + opts: opts, +): { title: string; description: string } { + switch (opts.flowType) { + case FlowType.Recovery: + return { + title: "Recover your account", + description: + "Enter the email address associated with your account to receive a one-time access code", + } + case FlowType.Settings: + return { + title: "Update your account", + description: "Update your account settings", + } + case FlowType.Verification: + return { + title: "Verify your account", + description: + "Enter the email address associated with your account to verify it", + } + } + + const parts = [] + + if (nodes.find((node) => node.group === "password")) { + switch (opts.flowType) { + case FlowType.Registration: + parts.push(`your email and a password`) + break + default: + parts.push(`your email and password`) + } + } + if (nodes.find((node) => node.group === "oidc")) { + parts.push(`a social provider`) + } + + if (nodes.find((node) => node.group === "code")) { + parts.push(`a code sent to your email`) + } + + if (nodes.find((node) => node.group === "passkey")) { + parts.push(`a Passkey`) + } + + if (nodes.find((node) => node.group === "webauthn")) { + parts.push(`a security key`) + } + + if (nodes.find((node) => node.group === "identifier_first")) { + const identifier = nodes.find( + (node) => + isUiNodeInputAttributes(node.attributes) && + node.attributes.name.startsWith("identifier") && + node.attributes.type !== "hidden", + ) + + if (identifier) { + parts.push(`your ${identifier.meta.label?.text}`) + } + } + + switch (opts.flowType) { + case FlowType.Login: + if (opts.flow.refresh) { + return { + title: "Reauthenticate", + description: "Confirm your identity with " + joinWithCommaOr(parts), + } + } + return { + title: "Sign in", + description: + parts.length > 0 ? "Sign in with " + joinWithCommaOr(parts) : "", + } + case FlowType.Registration: + return { + title: "Sign up", + description: + parts.length > 0 ? "Sign up with " + joinWithCommaOr(parts) : "", + } + } + + // TODO: This should not happen, as the switch is exhaustive, but typescript doesn't think so + return { + title: "Error", + description: "An error occurred", + } +} diff --git a/packages/elements-react/tsconfig.json b/packages/elements-react/tsconfig.json index 04ec34130..549242d99 100644 --- a/packages/elements-react/tsconfig.json +++ b/packages/elements-react/tsconfig.json @@ -19,7 +19,8 @@ "lib": ["ES6", "DOM", "WebWorker"], "rootDir": "src", "paths": { - "@ory/elements-react": ["./src/index.ts"] + "@ory/elements-react": ["./src/index.ts"], + "@tests/*": ["./src/tests/*"] } }, "exclude": ["node_modules"],